From a18558a4a7a2dd3d1112c7ae3e92b09d3b1b8d5c Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 16 Oct 2026 01:31:02 -0600 Subject: [PATCH 01/11] Stage isolated Worker split and verify deployment name handling --- .gitignore | 2 + docs/docs-html-cache-experiment.ts.txt | 55 + docs/docs-performance-measurements.json | 194 + docs/docs-performance-spike.md | 162 + ...s-production-performance-measurements.json | 3188 +++++++ docs/worker-cold-start-investigation.md | 109 + docs/worker-cold-start-measurements.json | 8123 +++++++++++++++++ docs/worker-cold-start-probes/README.md | 20 + docs/worker-cold-start-probes/measure.py.txt | 16 + docs/worker-cold-start-probes/probe.js.txt | 34 + .../worker-cold-start-probes/rebundle.mjs.txt | 11 + docs/worker-deployment-boundaries.md | 84 + docs/worker-split-measurements.json | 1714 ++++ docs/worker-split-validation.md | 105 + .../core/catalog-detail-http.test.ts | 4 +- harness-tests/core/chat-entrypoints.test.js | 120 + harness-tests/core/chat-file-route.test.ts | 2 +- harness-tests/core/chat-mcp-route.test.ts | 2 +- harness-tests/core/chat-model-route.test.ts | 2 +- harness-tests/core/chat-plugin-route.test.ts | 2 +- .../core/chat-runtime-headers.test.ts | 4 +- harness-tests/core/chat-skill-route.test.ts | 2 +- package.json | 9 +- scripts/build-worker-staging-baseline.mjs | 38 + scripts/chat-isolation-vite.ts | 58 + scripts/deploy-worker-staging.mjs | 67 + scripts/measure-worker-staging.py | 42 + scripts/prepare-worker-staging.mjs | 48 + scripts/worker-isolation-vite.ts | 48 + .../api/http/compile-attributed.ts | 52 + src/application-starter/api/http/compile.ts | 53 + .../api/http/deploy/check-name.ts | 85 + .../api/http/deploy/github.ts | 341 + src/application-starter/api/http/download.ts | 144 + .../api/http/feature-artifacts.ts | 59 + src/application-starter/api/http/features.ts | 33 + .../api/http/load-remote-addon.ts | 39 + .../api/http/load-remote-template.ts | 39 + .../api/http/load-template.ts | 54 + src/application-starter/api/http/resolve.ts | 207 + src/application-starter/api/http/routes.ts | 29 + src/application-starter/api/http/suggest.ts | 51 + src/application-starter/api/http/validate.ts | 51 + .../api/request-boundary.server.ts | 2 +- src/chat/components/AppRoute.tsx | 20 + src/chat/components/ChatShellRoute.tsx | 20 + src/chat/components/projects/Loading.tsx | 13 + src/chat/server/entries.ts | 487 + .../composer-drafts/$accountId.$scope.ts | 19 + .../server/http/api/chat/account/devices.ts | 15 + .../http/api/chat/account/onboarding.ts | 15 + .../http/api/chat/account/preferences.ts | 15 + src/chat/server/http/api/chat/activity.ts | 10 + src/chat/server/http/api/chat/app-version.ts | 12 + src/chat/server/http/api/chat/bootstrap.ts | 10 + .../bot-drafts/$id.files.$fileId.content.ts | 13 + .../api/chat/bot-drafts/$id.files.$fileId.ts | 25 + .../http/api/chat/bot-drafts/$id.files.ts | 25 + .../server/http/api/chat/bot-drafts/$id.ts | 19 + src/chat/server/http/api/chat/bots.ts | 10 + .../http/api/chat/bots/$id.$operation.ts | 36 + .../chat/bots/$id.files.$fileId.content.ts | 13 + .../http/api/chat/bots/$id.files.$fileId.ts | 25 + .../server/http/api/chat/bots/$id.files.ts | 25 + src/chat/server/http/api/chat/bots/$id.ts | 14 + src/chat/server/http/api/chat/bots/bulk.ts | 10 + src/chat/server/http/api/chat/bots/move.ts | 10 + src/chat/server/http/api/chat/connection.ts | 10 + .../api/chat/conversations/$id.$operation.ts | 76 + .../$id.execution.snapshots.$snapshotId.ts | 19 + .../$id.files.$fileId.content.ts | 19 + .../chat/conversations/$id.files.$fileId.ts | 25 + .../http/api/chat/conversations/$id.files.ts | 25 + .../$id.workflow-runs.$runId.cancel.ts | 14 + .../conversations/$id.workflow-runs.$runId.ts | 19 + .../$id.workflows.$workflowId.ts | 14 + .../chat/conversations/$id/files/import.ts | 14 + .../api/chat/conversations/$id/memories.ts | 17 + .../conversations/$id/memories/$memoryId.ts | 17 + .../api/chat/conversations/$id/references.ts | 14 + src/chat/server/http/api/chat/copies/$id.ts | 14 + .../server/http/api/chat/device-transport.ts | 10 + src/chat/server/http/api/chat/draft.ts | 13 + src/chat/server/http/api/chat/files.ts | 13 + src/chat/server/http/api/chat/kody/account.ts | 10 + .../server/http/api/chat/kody/callback.ts | 10 + .../server/http/api/chat/kody/community.ts | 10 + .../api/chat/kody/community/$listingId.ts | 14 + src/chat/server/http/api/chat/kody/connect.ts | 10 + .../http/api/chat/kody/jobs/$jobId/enabled.ts | 14 + .../server/http/api/chat/kody/mail/inboxes.ts | 10 + .../http/api/chat/kody/mail/messages.ts | 10 + .../api/chat/kody/mail/messages/$messageId.ts | 14 + .../server/http/api/chat/kody/memories.ts | 10 + .../http/api/chat/kody/memories/$memoryId.ts | 14 + .../api/chat/kody/memories/$memoryId/apply.ts | 14 + .../chat/kody/memories/$memoryId/review.ts | 14 + .../api/chat/kody/memories/create/apply.ts | 14 + .../api/chat/kody/memories/create/review.ts | 14 + .../chat/kody/packages/$packageId/document.ts | 14 + .../http/api/chat/kody/resources/$kind.ts | 14 + src/chat/server/http/api/chat/kody/runs.ts | 10 + .../server/http/api/chat/kody/runs/$runId.ts | 14 + .../http/api/chat/kody/runs/$runId/triage.ts | 14 + src/chat/server/http/api/chat/kody/servers.ts | 10 + .../api/chat/kody/servers/$serverId/check.ts | 14 + .../chat/kody/servers/$serverId/enabled.ts | 14 + .../chat/kody/servers/$serverId/reconnect.ts | 14 + src/chat/server/http/api/chat/kody/sync.ts | 10 + src/chat/server/http/api/chat/kody/unlink.ts | 10 + src/chat/server/http/api/chat/kody/usage.ts | 10 + src/chat/server/http/api/chat/mcp/$.ts | 17 + src/chat/server/http/api/chat/mcp/contract.ts | 10 + .../server/http/api/chat/model-options.ts | 10 + src/chat/server/http/api/chat/models.ts | 9 + .../http/api/chat/openrouter/callback.ts | 10 + src/chat/server/http/api/chat/plugins.ts | 13 + src/chat/server/http/api/chat/plugins/$id.ts | 17 + src/chat/server/http/api/chat/policy.ts | 10 + .../project-snapshots.$hash.quarantine.ts | 17 + .../http/api/chat/project-snapshots.$hash.ts | 47 + .../server/http/api/chat/project-snapshots.ts | 9 + .../server/http/api/chat/projects.$id.sync.ts | 25 + src/chat/server/http/api/chat/projects.$id.ts | 27 + src/chat/server/http/api/chat/projects.ts | 13 + src/chat/server/http/api/chat/recipes.ts | 10 + src/chat/server/http/api/chat/recipes/$id.ts | 14 + src/chat/server/http/api/chat/references.ts | 10 + .../http/api/chat/references/kody/inspect.ts | 14 + .../http/api/chat/references/kody/refresh.ts | 14 + .../http/api/chat/references/tools/refresh.ts | 14 + .../http/api/chat/retries/$id.prepare.ts | 14 + src/chat/server/http/api/chat/retries/$id.ts | 14 + src/chat/server/http/api/chat/sections.ts | 10 + src/chat/server/http/api/chat/sections/$id.ts | 19 + src/chat/server/http/api/chat/skills.ts | 13 + src/chat/server/http/api/chat/skills/$id.ts | 17 + .../server/http/api/chat/workspace-history.ts | 10 + .../http/api/chat/workspace-index.activity.ts | 14 + .../server/http/api/chat/workspace-index.ts | 10 + .../api/chat/workspace-sync/$operation.ts | 14 + src/chat/server/http/api/chat/workspace.ts | 23 + src/chat/server/http/auth/mcp/callback.ts | 10 + .../server/http/auth/mcp/client-metadata.ts | 10 + src/chat/server/http/routes.ts | 196 + src/chat/server/routes/draft.ts | 43 + src/chat/server/routes/files.ts | 84 + src/chat/server/routes/mcp/$.ts | 62 + src/chat/server/routes/models.ts | 40 + src/chat/server/routes/openrouter/callback.ts | 34 + src/chat/server/routes/plugins.ts | 54 + .../project-snapshots.$hash.quarantine.ts | 163 + src/chat/server/routes/project-snapshots.ts | 108 + src/chat/server/routes/projects.$id.sync.ts | 536 ++ src/chat/server/routes/projects.$id.ts | 364 + src/chat/server/routes/projects.ts | 275 + src/chat/server/routes/skills.ts | 54 + src/components/Navbar.tsx | 2 +- src/components/Scarf.tsx | 7 +- src/components/ds/IconographyPage.client.tsx | 219 + src/db/client.ts | 10 +- src/queries/docsManifest.ts | 16 + src/router.tsx | 2 +- src/routes/__root.tsx | 34 +- .../_library/$libraryId/$version.docs.$.tsx | 2 + .../$version.docs.framework.$framework.$.tsx | 2 + src/routes/admin/chat-access.tsx | 2 +- .../application-starter/compile-attributed.ts | 48 +- src/routes/api/application-starter/compile.ts | 43 +- .../application-starter/deploy/check-name.ts | 86 +- .../api/application-starter/deploy/github.ts | 333 +- .../api/application-starter/download.ts | 142 +- .../application-starter/feature-artifacts.ts | 57 +- .../api/application-starter/features.ts | 25 +- .../application-starter/load-remote-addon.ts | 37 +- .../load-remote-template.ts | 35 +- .../api/application-starter/load-template.ts | 51 +- src/routes/api/application-starter/resolve.ts | 201 +- src/routes/api/application-starter/suggest.ts | 38 +- .../api/application-starter/validate.ts | 41 +- .../composer-drafts/$accountId.$scope.ts | 17 +- src/routes/api/chat/account/devices.ts | 14 +- src/routes/api/chat/account/onboarding.ts | 10 +- src/routes/api/chat/account/preferences.ts | 10 +- src/routes/api/chat/activity.ts | 13 +- src/routes/api/chat/app-version.ts | 11 +- src/routes/api/chat/bootstrap.ts | 11 +- .../bot-drafts/$id.files.$fileId.content.ts | 17 +- .../api/chat/bot-drafts/$id.files.$fileId.ts | 21 +- src/routes/api/chat/bot-drafts/$id.files.ts | 21 +- src/routes/api/chat/bot-drafts/$id.ts | 14 +- src/routes/api/chat/bots.ts | 13 +- src/routes/api/chat/bots/$id.$operation.ts | 11 +- .../chat/bots/$id.files.$fileId.content.ts | 17 +- src/routes/api/chat/bots/$id.files.$fileId.ts | 21 +- src/routes/api/chat/bots/$id.files.ts | 21 +- src/routes/api/chat/bots/$id.ts | 13 +- src/routes/api/chat/bots/bulk.ts | 13 +- src/routes/api/chat/bots/move.ts | 13 +- src/routes/api/chat/connection.ts | 13 +- .../api/chat/conversations/$id.$operation.ts | 15 +- .../$id.execution.snapshots.$snapshotId.ts | 19 +- .../$id.files.$fileId.content.ts | 17 +- .../chat/conversations/$id.files.$fileId.ts | 23 +- .../api/chat/conversations/$id.files.ts | 21 +- .../$id.workflow-runs.$runId.cancel.ts | 15 +- .../conversations/$id.workflow-runs.$runId.ts | 19 +- .../$id.workflows.$workflowId.ts | 15 +- .../chat/conversations/$id/files/import.ts | 15 +- .../api/chat/conversations/$id/memories.ts | 14 +- .../conversations/$id/memories/$memoryId.ts | 16 +- .../api/chat/conversations/$id/references.ts | 13 +- src/routes/api/chat/copies/$id.ts | 13 +- src/routes/api/chat/device-transport.ts | 13 +- src/routes/api/chat/draft.ts | 37 +- src/routes/api/chat/files.ts | 86 +- src/routes/api/chat/kody/account.ts | 13 +- src/routes/api/chat/kody/callback.ts | 11 +- src/routes/api/chat/kody/community.ts | 13 +- .../api/chat/kody/community/$listingId.ts | 13 +- src/routes/api/chat/kody/connect.ts | 11 +- .../api/chat/kody/jobs/$jobId/enabled.ts | 13 +- src/routes/api/chat/kody/mail/inboxes.ts | 13 +- src/routes/api/chat/kody/mail/messages.ts | 13 +- .../api/chat/kody/mail/messages/$messageId.ts | 15 +- src/routes/api/chat/kody/memories.ts | 13 +- .../api/chat/kody/memories/$memoryId.ts | 13 +- .../api/chat/kody/memories/$memoryId/apply.ts | 15 +- .../chat/kody/memories/$memoryId/review.ts | 15 +- .../api/chat/kody/memories/create/apply.ts | 13 +- .../api/chat/kody/memories/create/review.ts | 13 +- .../chat/kody/packages/$packageId/document.ts | 15 +- src/routes/api/chat/kody/resources/$kind.ts | 13 +- src/routes/api/chat/kody/runs.ts | 13 +- src/routes/api/chat/kody/runs/$runId.ts | 13 +- .../api/chat/kody/runs/$runId/triage.ts | 13 +- src/routes/api/chat/kody/servers.ts | 13 +- .../api/chat/kody/servers/$serverId/check.ts | 13 +- .../chat/kody/servers/$serverId/enabled.ts | 15 +- .../chat/kody/servers/$serverId/reconnect.ts | 15 +- src/routes/api/chat/kody/sync.ts | 13 +- src/routes/api/chat/kody/unlink.ts | 13 +- src/routes/api/chat/kody/usage.ts | 13 +- src/routes/api/chat/mcp/$.ts | 41 +- src/routes/api/chat/mcp/contract.ts | 13 +- src/routes/api/chat/model-options.ts | 13 +- src/routes/api/chat/models.ts | 47 +- src/routes/api/chat/openrouter/callback.ts | 38 +- src/routes/api/chat/plugins.ts | 37 +- src/routes/api/chat/plugins/$id.ts | 6 +- src/routes/api/chat/policy.ts | 13 +- .../project-snapshots.$hash.quarantine.ts | 164 +- .../api/chat/project-snapshots.$hash.ts | 39 +- src/routes/api/chat/project-snapshots.ts | 112 +- src/routes/api/chat/projects.$id.sync.ts | 553 +- src/routes/api/chat/projects.$id.ts | 382 +- src/routes/api/chat/projects.ts | 291 +- src/routes/api/chat/recipes.ts | 13 +- src/routes/api/chat/recipes/$id.ts | 13 +- src/routes/api/chat/references.ts | 13 +- .../api/chat/references/kody/inspect.ts | 13 +- .../api/chat/references/kody/refresh.ts | 13 +- .../api/chat/references/tools/refresh.ts | 13 +- src/routes/api/chat/retries/$id.prepare.ts | 13 +- src/routes/api/chat/retries/$id.ts | 13 +- src/routes/api/chat/sections.ts | 11 +- src/routes/api/chat/sections/$id.ts | 12 +- src/routes/api/chat/skills.ts | 37 +- src/routes/api/chat/skills/$id.ts | 6 +- src/routes/api/chat/workspace-history.ts | 13 +- .../api/chat/workspace-index.activity.ts | 13 +- src/routes/api/chat/workspace-index.ts | 9 +- .../api/chat/workspace-sync/$operation.ts | 9 +- src/routes/api/chat/workspace.ts | 16 +- src/routes/api/og/{$}[.]png.ts | 60 +- src/routes/api/readme/{$}[.]png.ts | 97 +- src/routes/auth/mcp/callback.ts | 9 +- src/routes/auth/mcp/client-metadata.ts | 9 +- src/routes/chat.c.$conversationId.tsx | 4 +- src/routes/chat.charts.tsx | 11 +- src/routes/chat.new-project.tsx | 2 +- src/routes/chat.p.$hash.tsx | 11 +- src/routes/chat.project.$projectId.tsx | 2 +- src/routes/chat.shared.tsx | 11 +- src/routes/chat.tsx | 22 +- src/routes/chat.w.$workspaceId.b.$botId.tsx | 2 +- src/routes/chat.w.$workspaceId.tsx | 4 +- src/routes/ds.iconography.lazy.tsx | 237 +- src/server.ts | 51 +- src/server/og/og-http.server.ts | 56 + src/server/og/readme-http.server.ts | 92 + src/server/runtime/http-route.server.ts | 50 + src/server/runtime/worker-service.server.ts | 25 + src/server/scheduled.server.ts | 6 +- src/types/durable-streams.d.ts | 10 + src/utils/analytics/providers/google.ts | 6 +- src/utils/analytics/scarf.ts | 8 +- src/utils/docs-cache-headers.ts | 9 +- src/utils/docs-html-headers.server.ts | 37 + src/utils/docs.functions.ts | 1 + src/utils/docs.ts | 36 +- src/utils/github-content-cache.server.ts | 87 +- src/utils/request-timing.server.ts | 136 + src/utils/sentry.server.ts | 4 +- src/workers/chat.ts | 49 + src/workers/og.ts | 25 + src/workers/starter.ts | 28 + staging/README.md | 69 + staging/baseline.patch | 417 + staging/browser.json | 10 + staging/deployments.json | 8 + staging/measurements.json | 882 ++ staging/state-after-rollback.json | 44 + staging/state-before-rollback.json | 44 + staging/state-final.json | 44 + tests/builder-openrouter.test.ts | 2 +- tests/builder-project-quarantine.test.ts | 2 +- tests/chat-entrypoints.test.ts | 51 + tests/docs-html-headers.test.ts | 48 + tests/docs-manifest-cache.test.ts | 92 + tests/github-content-cache.test.ts | 57 + tests/request-timing.test.ts | 51 + tests/worker-boundaries.test.ts | 172 + tests/worker-deployment.test.ts | 90 + vite.config.ts | 62 +- workers/chat/wrangler.jsonc | 83 + workers/og/wrangler.jsonc | 21 + workers/starter/wrangler.jsonc | 32 + wrangler.jsonc | 50 +- 329 files changed, 23754 insertions(+), 3719 deletions(-) create mode 100644 docs/docs-html-cache-experiment.ts.txt create mode 100644 docs/docs-performance-measurements.json create mode 100644 docs/docs-performance-spike.md create mode 100644 docs/docs-production-performance-measurements.json create mode 100644 docs/worker-cold-start-investigation.md create mode 100644 docs/worker-cold-start-measurements.json create mode 100644 docs/worker-cold-start-probes/README.md create mode 100644 docs/worker-cold-start-probes/measure.py.txt create mode 100644 docs/worker-cold-start-probes/probe.js.txt create mode 100644 docs/worker-cold-start-probes/rebundle.mjs.txt create mode 100644 docs/worker-deployment-boundaries.md create mode 100644 docs/worker-split-measurements.json create mode 100644 docs/worker-split-validation.md create mode 100644 harness-tests/core/chat-entrypoints.test.js create mode 100644 scripts/build-worker-staging-baseline.mjs create mode 100644 scripts/chat-isolation-vite.ts create mode 100644 scripts/deploy-worker-staging.mjs create mode 100644 scripts/measure-worker-staging.py create mode 100644 scripts/prepare-worker-staging.mjs create mode 100644 scripts/worker-isolation-vite.ts create mode 100644 src/application-starter/api/http/compile-attributed.ts create mode 100644 src/application-starter/api/http/compile.ts create mode 100644 src/application-starter/api/http/deploy/check-name.ts create mode 100644 src/application-starter/api/http/deploy/github.ts create mode 100644 src/application-starter/api/http/download.ts create mode 100644 src/application-starter/api/http/feature-artifacts.ts create mode 100644 src/application-starter/api/http/features.ts create mode 100644 src/application-starter/api/http/load-remote-addon.ts create mode 100644 src/application-starter/api/http/load-remote-template.ts create mode 100644 src/application-starter/api/http/load-template.ts create mode 100644 src/application-starter/api/http/resolve.ts create mode 100644 src/application-starter/api/http/routes.ts create mode 100644 src/application-starter/api/http/suggest.ts create mode 100644 src/application-starter/api/http/validate.ts create mode 100644 src/chat/components/AppRoute.tsx create mode 100644 src/chat/components/ChatShellRoute.tsx create mode 100644 src/chat/server/entries.ts create mode 100644 src/chat/server/http/api/chat/account/composer-drafts/$accountId.$scope.ts create mode 100644 src/chat/server/http/api/chat/account/devices.ts create mode 100644 src/chat/server/http/api/chat/account/onboarding.ts create mode 100644 src/chat/server/http/api/chat/account/preferences.ts create mode 100644 src/chat/server/http/api/chat/activity.ts create mode 100644 src/chat/server/http/api/chat/app-version.ts create mode 100644 src/chat/server/http/api/chat/bootstrap.ts create mode 100644 src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.content.ts create mode 100644 src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.ts create mode 100644 src/chat/server/http/api/chat/bot-drafts/$id.files.ts create mode 100644 src/chat/server/http/api/chat/bot-drafts/$id.ts create mode 100644 src/chat/server/http/api/chat/bots.ts create mode 100644 src/chat/server/http/api/chat/bots/$id.$operation.ts create mode 100644 src/chat/server/http/api/chat/bots/$id.files.$fileId.content.ts create mode 100644 src/chat/server/http/api/chat/bots/$id.files.$fileId.ts create mode 100644 src/chat/server/http/api/chat/bots/$id.files.ts create mode 100644 src/chat/server/http/api/chat/bots/$id.ts create mode 100644 src/chat/server/http/api/chat/bots/bulk.ts create mode 100644 src/chat/server/http/api/chat/bots/move.ts create mode 100644 src/chat/server/http/api/chat/connection.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.$operation.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.files.$fileId.content.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.files.$fileId.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.files.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id.workflows.$workflowId.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id/files/import.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id/memories.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id/memories/$memoryId.ts create mode 100644 src/chat/server/http/api/chat/conversations/$id/references.ts create mode 100644 src/chat/server/http/api/chat/copies/$id.ts create mode 100644 src/chat/server/http/api/chat/device-transport.ts create mode 100644 src/chat/server/http/api/chat/draft.ts create mode 100644 src/chat/server/http/api/chat/files.ts create mode 100644 src/chat/server/http/api/chat/kody/account.ts create mode 100644 src/chat/server/http/api/chat/kody/callback.ts create mode 100644 src/chat/server/http/api/chat/kody/community.ts create mode 100644 src/chat/server/http/api/chat/kody/community/$listingId.ts create mode 100644 src/chat/server/http/api/chat/kody/connect.ts create mode 100644 src/chat/server/http/api/chat/kody/jobs/$jobId/enabled.ts create mode 100644 src/chat/server/http/api/chat/kody/mail/inboxes.ts create mode 100644 src/chat/server/http/api/chat/kody/mail/messages.ts create mode 100644 src/chat/server/http/api/chat/kody/mail/messages/$messageId.ts create mode 100644 src/chat/server/http/api/chat/kody/memories.ts create mode 100644 src/chat/server/http/api/chat/kody/memories/$memoryId.ts create mode 100644 src/chat/server/http/api/chat/kody/memories/$memoryId/apply.ts create mode 100644 src/chat/server/http/api/chat/kody/memories/$memoryId/review.ts create mode 100644 src/chat/server/http/api/chat/kody/memories/create/apply.ts create mode 100644 src/chat/server/http/api/chat/kody/memories/create/review.ts create mode 100644 src/chat/server/http/api/chat/kody/packages/$packageId/document.ts create mode 100644 src/chat/server/http/api/chat/kody/resources/$kind.ts create mode 100644 src/chat/server/http/api/chat/kody/runs.ts create mode 100644 src/chat/server/http/api/chat/kody/runs/$runId.ts create mode 100644 src/chat/server/http/api/chat/kody/runs/$runId/triage.ts create mode 100644 src/chat/server/http/api/chat/kody/servers.ts create mode 100644 src/chat/server/http/api/chat/kody/servers/$serverId/check.ts create mode 100644 src/chat/server/http/api/chat/kody/servers/$serverId/enabled.ts create mode 100644 src/chat/server/http/api/chat/kody/servers/$serverId/reconnect.ts create mode 100644 src/chat/server/http/api/chat/kody/sync.ts create mode 100644 src/chat/server/http/api/chat/kody/unlink.ts create mode 100644 src/chat/server/http/api/chat/kody/usage.ts create mode 100644 src/chat/server/http/api/chat/mcp/$.ts create mode 100644 src/chat/server/http/api/chat/mcp/contract.ts create mode 100644 src/chat/server/http/api/chat/model-options.ts create mode 100644 src/chat/server/http/api/chat/models.ts create mode 100644 src/chat/server/http/api/chat/openrouter/callback.ts create mode 100644 src/chat/server/http/api/chat/plugins.ts create mode 100644 src/chat/server/http/api/chat/plugins/$id.ts create mode 100644 src/chat/server/http/api/chat/policy.ts create mode 100644 src/chat/server/http/api/chat/project-snapshots.$hash.quarantine.ts create mode 100644 src/chat/server/http/api/chat/project-snapshots.$hash.ts create mode 100644 src/chat/server/http/api/chat/project-snapshots.ts create mode 100644 src/chat/server/http/api/chat/projects.$id.sync.ts create mode 100644 src/chat/server/http/api/chat/projects.$id.ts create mode 100644 src/chat/server/http/api/chat/projects.ts create mode 100644 src/chat/server/http/api/chat/recipes.ts create mode 100644 src/chat/server/http/api/chat/recipes/$id.ts create mode 100644 src/chat/server/http/api/chat/references.ts create mode 100644 src/chat/server/http/api/chat/references/kody/inspect.ts create mode 100644 src/chat/server/http/api/chat/references/kody/refresh.ts create mode 100644 src/chat/server/http/api/chat/references/tools/refresh.ts create mode 100644 src/chat/server/http/api/chat/retries/$id.prepare.ts create mode 100644 src/chat/server/http/api/chat/retries/$id.ts create mode 100644 src/chat/server/http/api/chat/sections.ts create mode 100644 src/chat/server/http/api/chat/sections/$id.ts create mode 100644 src/chat/server/http/api/chat/skills.ts create mode 100644 src/chat/server/http/api/chat/skills/$id.ts create mode 100644 src/chat/server/http/api/chat/workspace-history.ts create mode 100644 src/chat/server/http/api/chat/workspace-index.activity.ts create mode 100644 src/chat/server/http/api/chat/workspace-index.ts create mode 100644 src/chat/server/http/api/chat/workspace-sync/$operation.ts create mode 100644 src/chat/server/http/api/chat/workspace.ts create mode 100644 src/chat/server/http/auth/mcp/callback.ts create mode 100644 src/chat/server/http/auth/mcp/client-metadata.ts create mode 100644 src/chat/server/http/routes.ts create mode 100644 src/chat/server/routes/draft.ts create mode 100644 src/chat/server/routes/files.ts create mode 100644 src/chat/server/routes/mcp/$.ts create mode 100644 src/chat/server/routes/models.ts create mode 100644 src/chat/server/routes/openrouter/callback.ts create mode 100644 src/chat/server/routes/plugins.ts create mode 100644 src/chat/server/routes/project-snapshots.$hash.quarantine.ts create mode 100644 src/chat/server/routes/project-snapshots.ts create mode 100644 src/chat/server/routes/projects.$id.sync.ts create mode 100644 src/chat/server/routes/projects.$id.ts create mode 100644 src/chat/server/routes/projects.ts create mode 100644 src/chat/server/routes/skills.ts create mode 100644 src/components/ds/IconographyPage.client.tsx create mode 100644 src/queries/docsManifest.ts create mode 100644 src/server/og/og-http.server.ts create mode 100644 src/server/og/readme-http.server.ts create mode 100644 src/server/runtime/http-route.server.ts create mode 100644 src/server/runtime/worker-service.server.ts create mode 100644 src/types/durable-streams.d.ts create mode 100644 src/utils/docs-html-headers.server.ts create mode 100644 src/utils/request-timing.server.ts create mode 100644 src/workers/chat.ts create mode 100644 src/workers/og.ts create mode 100644 src/workers/starter.ts create mode 100644 staging/README.md create mode 100644 staging/baseline.patch create mode 100644 staging/browser.json create mode 100644 staging/deployments.json create mode 100644 staging/measurements.json create mode 100644 staging/state-after-rollback.json create mode 100644 staging/state-before-rollback.json create mode 100644 staging/state-final.json create mode 100644 tests/chat-entrypoints.test.ts create mode 100644 tests/docs-html-headers.test.ts create mode 100644 tests/docs-manifest-cache.test.ts create mode 100644 tests/request-timing.test.ts create mode 100644 tests/worker-boundaries.test.ts create mode 100644 tests/worker-deployment.test.ts create mode 100644 workers/chat/wrangler.jsonc create mode 100644 workers/og/wrangler.jsonc create mode 100644 workers/starter/wrangler.jsonc diff --git a/.gitignore b/.gitignore index 9e5a97423..2f5bf34ce 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ src/routeTree.gen.ts # Generated native desktop helper /desktop/native/folder-access + +.staging/ diff --git a/docs/docs-html-cache-experiment.ts.txt b/docs/docs-html-cache-experiment.ts.txt new file mode 100644 index 000000000..f142cf982 --- /dev/null +++ b/docs/docs-html-cache-experiment.ts.txt @@ -0,0 +1,55 @@ +// Archived proof of concept. Not imported by the application. + +export async function serveDocsHtml( + request: Request, + options: { + cache: Pick | undefined + render: () => Response | Promise + waitUntil: (task: Promise) => void + }, +) { + const eligible = isPublicDocsHtmlRequest(request) + // A separate key prevents collisions with negotiated Markdown responses. + const key = new URL(request.url) + key.searchParams.set('__docs_html_cache', 'v1') + if (eligible && options.cache) { + const hit = await options.cache.match(key.href).catch((error) => { + console.warn('[Docs HTML cache] Read failed', error) + return undefined + }) + if (hit) { + const headers = new Headers(hit.headers) + headers.set('X-Docs-Cache', 'HIT') + const [, libraryId, version] = new URL(request.url).pathname.split('/') + headers.set( + 'Cache-Control', + getDocsCacheHeaders({ libraryId, version })['Cache-Control'], + ) + return new Response(hit.body, { status: hit.status, headers }) + } + } + const response = finalizeDocsHtmlHeaders(request, await options.render()) + if ( + eligible && + options.cache && + response.status === 200 && + response.headers.get('content-type')?.includes('text/html') && + response.headers.get('cache-tag')?.split(',').includes('docs:all') && + !response.headers.has('set-cookie') && + !/no-store|private/i.test(response.headers.get('cache-control') ?? '') + ) { + // Cache API uses Cache-Control, not the CDN header. Keep this spike's + // HTML TTL at 60 seconds; content refreshes independently in waitUntil. + const headers = new Headers(response.headers) + headers.set('Cache-Control', 'public, max-age=60') + options.waitUntil( + options.cache + .put(key.href, new Response(response.clone().body, { headers })) + .catch((error) => { + console.warn('[Docs HTML cache] Write failed', error) + }), + ) + response.headers.set('X-Docs-Cache', 'MISS') + } + return response +} diff --git a/docs/docs-performance-measurements.json b/docs/docs-performance-measurements.json new file mode 100644 index 000000000..5a237297e --- /dev/null +++ b/docs/docs-performance-measurements.json @@ -0,0 +1,194 @@ +[ + { + "request": 1, + "status": 200, + "ttfb_ms": 1779.9, + "total_ms": 1900.0, + "tls_ms": 128.1, + "server_timing": "app_headers;dur=256.0, db_config;dur=0.0, content_edge_read;dur=504.0, start_handler;dur=256.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cache_control": "private, no-store", + "ray": "a4b03dcc0e46f0aa", + "instance": "d91be598-ef5a-4d60-965f-2579acc5d50a", + "docs_request": 1, + "wall_ms": 424, + "cpu_ms": 166, + "clock_ms": 256, + "db_used": false + }, + { + "request": 2, + "status": 200, + "ttfb_ms": 1713.9, + "total_ms": 1831.7, + "tls_ms": 85.0, + "server_timing": "app_headers;dur=243.0, db_config;dur=0.0, content_edge_read;dur=480.0, start_handler;dur=243.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cache_control": "private, no-store", + "ray": "a4b03dd7cda61e3c", + "instance": "041c4fde-fa09-4ade-a9cd-17386142d366", + "docs_request": 1, + "wall_ms": 411, + "cpu_ms": 163, + "clock_ms": 243, + "db_used": false + }, + { + "request": 3, + "status": 200, + "ttfb_ms": 286.8, + "total_ms": 412.8, + "tls_ms": 84.0, + "server_timing": "app_headers;dur=19.0, db_config;dur=0.0, content_edge_read;dur=25.0, start_handler;dur=19.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "cache_control": "private, no-store", + "ray": "a4b03de3583bf0ba", + "instance": "d91be598-ef5a-4d60-965f-2579acc5d50a", + "docs_request": 2, + "wall_ms": 99, + "cpu_ms": 63, + "clock_ms": 19, + "db_used": false + }, + { + "request": 4, + "status": 200, + "ttfb_ms": 3243.7, + "total_ms": 3361.7, + "tls_ms": 122.5, + "server_timing": "app_headers;dur=507.0, db_config;dur=0.0, content_edge_read;dur=1004.0, start_handler;dur=507.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cache_control": "private, no-store", + "ray": "a4b03de63a396b3f", + "instance": "c995ad13-f3e8-47dc-80a3-7e53db2cc399", + "docs_request": 1, + "wall_ms": 955, + "cpu_ms": 443, + "clock_ms": 507, + "db_used": false + }, + { + "request": 5, + "status": 200, + "ttfb_ms": 3008.6, + "total_ms": 3133.4, + "tls_ms": 80.2, + "server_timing": "app_headers;dur=408.0, db_config;dur=0.0, content_edge_read;dur=806.0, start_handler;dur=408.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cache_control": "private, no-store", + "ray": "a4b03dfb0ad62e5a", + "instance": "df765ba1-16b1-46c9-a22a-522a4ca6fa6c", + "docs_request": 1, + "wall_ms": 872, + "cpu_ms": 469, + "clock_ms": 408, + "db_used": false + }, + { + "request": 6, + "status": 200, + "ttfb_ms": 1747.5, + "total_ms": 1866.6, + "tls_ms": 80.8, + "server_timing": "app_headers;dur=248.0, db_config;dur=0.0, content_edge_read;dur=489.0, start_handler;dur=248.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cache_control": "private, no-store", + "ray": "a4b03e0ebffd21d2", + "instance": "a965714b-3f6f-47bf-b76f-b9eda86c923c", + "docs_request": 1, + "wall_ms": 422, + "cpu_ms": 167, + "clock_ms": 248, + "db_used": false + }, + { + "request": 1, + "status": 200, + "ttfb_ms": 2192.4, + "total_ms": 2314.2, + "tls_ms": 80.1, + "server_timing": "app_headers;dur=489.0, db_config;dur=0.0, content_edge_read;dur=970.0, start_handler;dur=489.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cache_control": "private, no-store", + "ray": "a4b03e97be5ffe48", + "instance": "34c797bd-a9ad-4270-a8c8-56f1611201b3", + "docs_request": 1, + "wall_ms": 683, + "cpu_ms": 192, + "clock_ms": 489, + "db_used": false + }, + { + "request": 2, + "status": 200, + "ttfb_ms": 1991.8, + "total_ms": 2109.0, + "tls_ms": 80.6, + "server_timing": "app_headers;dur=305.0, db_config;dur=0.0, content_edge_read;dur=603.0, start_handler;dur=305.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cache_control": "private, no-store", + "ray": "a4b03ea65b2969fb", + "instance": "8d523fd5-9787-4cc8-810a-6e41e9bf1373", + "docs_request": 1, + "wall_ms": 486, + "cpu_ms": 177, + "clock_ms": 305, + "db_used": false + }, + { + "request": 3, + "status": 200, + "ttfb_ms": 274.7, + "total_ms": 391.4, + "tls_ms": 84.2, + "server_timing": "app_headers;dur=13.0, db_config;dur=0.0, content_edge_read;dur=19.0, start_handler;dur=13.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "cache_control": "private, no-store", + "ray": "a4b03eb3a8863cd3", + "instance": "34c797bd-a9ad-4270-a8c8-56f1611201b3", + "docs_request": 2, + "wall_ms": 92, + "cpu_ms": 64, + "clock_ms": 13, + "db_used": false + }, + { + "request": 4, + "status": 200, + "ttfb_ms": 242.1, + "total_ms": 363.0, + "tls_ms": 81.4, + "server_timing": "app_headers;dur=16.0, db_config;dur=0.0, content_edge_read;dur=23.0, start_handler;dur=16.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "cache_control": "private, no-store", + "ray": "a4b03eb62ad5f0b0", + "instance": "d91be598-ef5a-4d60-965f-2579acc5d50a", + "docs_request": 3, + "wall_ms": 79, + "cpu_ms": 51, + "clock_ms": 16, + "db_used": false + }, + { + "request": 5, + "status": 200, + "ttfb_ms": 248.4, + "total_ms": 363.3, + "tls_ms": 81.7, + "server_timing": "app_headers;dur=12.0, db_config;dur=0.0, content_edge_read;dur=19.0, start_handler;dur=12.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "cache_control": "private, no-store", + "ray": "a4b03eb8898adaec", + "instance": "a965714b-3f6f-47bf-b76f-b9eda86c923c", + "docs_request": 2, + "wall_ms": 93, + "cpu_ms": 60, + "clock_ms": 12, + "db_used": false + }, + { + "request": 6, + "status": 200, + "ttfb_ms": 272.6, + "total_ms": 388.5, + "tls_ms": 94.6, + "server_timing": "app_headers;dur=11.0, db_config;dur=0.0, content_edge_read;dur=16.0, start_handler;dur=11.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "cache_control": "private, no-store", + "ray": "a4b03ebafaab3127", + "instance": "34c797bd-a9ad-4270-a8c8-56f1611201b3", + "docs_request": 3, + "wall_ms": 108, + "cpu_ms": 88, + "clock_ms": 11, + "db_used": false + } +] diff --git a/docs/docs-performance-spike.md b/docs/docs-performance-spike.md new file mode 100644 index 000000000..594d0ad1f --- /dev/null +++ b/docs/docs-performance-spike.md @@ -0,0 +1,162 @@ +# Docs response performance investigation + +## Current implementation + +Complete-page caching is out of the request path. `wrangler.jsonc` still has Workers Cache disabled. HTML docs responses use `Cache-Control: private, no-store` and `Cloudflare-CDN-Cache-Control: no-store`. The earlier Cache API experiment is archived in `docs-html-cache-experiment.ts.txt`, which the application does not import. Production deployment and follow-up measurements are recorded in the final section below. No CDN purge was performed. An isolated temporary Worker was deployed for measurement on October 15, with no production routes, database binding, application secrets, or scheduled jobs. It used the existing docs R2 bucket. The temporary Worker was deleted after capture; the R2 bucket was retained. + +The retained changes are manifest reuse across browser navigation, final page header ownership, and background refresh of expired positive GitHub source content. Explicit source invalidation still refreshes synchronously. Document and sidebar purge tags remain available, but do not enable HTML caching. + +## Production observations, October 14, 2026 + +Three requests to `/router/latest/docs/routing/routing-concepts`, before deploying any of these changes: + +| Ray ID | Client TTFB | Cloudflare cfEdge | Cloudflare cfWorker | +| -------------------- | ----------: | ----------------: | ------------------: | +| a4a8f328c97c6b56-DFW | 3104ms | 79ms | 2817ms | +| a4a8f33c8aaa6de9-DFW | 3380ms | 2467ms | 780ms | +| a4a8f352784b1b62-DFW | 320ms | 44ms | 151ms | + +The live Worker tail captured the last two. Worker wall/CPU times were 787/323ms and 158/92ms. Existing application request-end logs reported 460ms and 28ms, with zero outbound HTTP fetches counted. Those counts do not include R2 bindings or database sockets, and request-end logs do not cover the entire streamed response. The first request was not captured by the tail. + +The slow requests cannot all be attributed to the same application phase. One has substantial Worker time; another has substantial Cloudflare-reported edge time. The measurements do not establish why either was slow. + +Docs source reads use R2 plus the content cache, falling back to GitHub. Postgres is initialized lazily, so entering the database request context does not itself issue a query. + +## New diagnostics + +Only docs-path requests receive these diagnostics: + +- `Server-Timing`: application time through response creation, Start handler, database configuration lookup, content edge-cache reads/writes, R2 reads/body reads, origin fetch work, artifact construction and persistence where executed. +- `db_used`: whether the database accessor was used by response-header creation. This is an access indicator, not a SQL query count or duration. +- Structured `docs_timing` logs: headers ready, first body byte, body completion, cancellation or stream failure. Logs include the Cloudflare ray ID, phase offsets/durations, and database access indicator at that stage. +- Sentry child spans for the measured operations, under the existing request tracing integration. + +Timings overlap and can be nested, so do not add their durations to calculate total latency. Header timings are a snapshot; work completed during streaming appears in later body-phase logs. Origin time includes the whole content-origin operation, including response-body consumption. These diagnostics do not measure Worker startup before the entrypoint or network transit to the visitor. + +Sentry tracing is configured at 100% with request and server-function middleware. Receipt of these new child spans in the production Sentry project has not been verified. Cloudflare Worker tail access was verified. + +## Deployed preview measurements, October 15 + +The temporary Worker ran the production build with complete HTML caching disabled. Twelve sequential requests across two runs all returned 200 and `private, no-store`, with no database access recorded. Requests reached DFW from the same machine. The instance ID and per-instance docs request counter distinguish first docs requests from reused instances. + +| Instance state | Requests | Client TTFB | Median TTFB | Platform CPU | Platform wall time | +| ------------------ | -------: | ------------: | ----------: | -----------: | -----------------: | +| First docs request | 7 | 1,714–3,244ms | 1,992ms | 163–469ms | 411–955ms | +| Reused instance | 5 | 242–287ms | 273ms | 51–88ms | 79–108ms | + +Individual samples and ray IDs are in `docs-performance-measurements.json`. This is a small preview sample, not a production percentile or a multi-region benchmark. First docs request is an observed counter, not a direct platform cold-start event. + +Fresh-instance work is strongly associated with the slow responses. The page does not require Postgres to render. This does not yet isolate which functions are expensive, nor explain all the gap between platform invocation timing and client TTFB. The Worker upload was approximately 8.4MB compressed and reported 345ms startup time on the first build, which makes module initialization worth profiling, but is not proof of the full cause. + +The content-phase spans showed initial parallel cache reads taking much longer on first docs requests. Their intervals are not a standalone benchmark of the cache service. + +### Clock limitation + +Cloudflare freezes `performance.now()` and `Date.now()` between I/O events in deployed Workers. The application phase timings can therefore miss CPU-only work, especially rendering after the last I/O. Do not treat `app_headers`, first-body-byte or body-complete clock values as complete real elapsed durations. Compare them with Cloudflare's platform CPU/wall counters and client TTFB. See https://developers.cloudflare.com/workers/runtime-apis/performance/. + +The CPU-profile API returned HTTP 404, “No recent executions were found for this Worker,” twice, even with successful traffic and Worker tail events. A function-level CPU profile was not obtained. Sentry delivery remains unverified. + +### Validation + +Production build, all TypeScript configurations and repository lint passed. Unit tests: 556 passed, 4 skipped. Chat tests: 2,269 passed. Desktop tests: 20 passed, 1 skipped. The initial unit command was blocked by the sandbox's IPC restriction; rerunning the remaining suites with IPC permission passed. The later instance marker passed its targeted tests and production build. + +## Next measurement + +Profile CPU for cold and reused instances to identify module initialization versus first-render work. Keep HTML caching disabled. If deploying the instrumentation to production, correlate `cf-ray` with `docs_timing` logs. Compare Cloudflare timing, headers-ready, first-body-byte and body-complete timing first. Then inspect the nested content phases and `databaseUsed`. If database access appears, instrument the specific query path rather than attributing all request time to Postgres. Compare multiple regions and cold/warm Worker runs. + +The local runtime uses development content sources and Vite compilation. Its timing is useful for verifying instrumentation, not for predicting production latency. + +## First-request CPU investigation, October 15 + +Profiled the production build locally with the workerd inspector, then used `wrangler check startup` for module initialization. No additional remote Worker was created. + +An initially empty local content store produced a 2,275ms first request, dominated by GitHub/tree retrieval and manifest construction. That is a content-cache miss, not a clean cold-instance comparison. After populating storage and restarting the Worker, the first request was 143ms and the repeated request 20ms, with no origin fetches in the first request's timing header. + +Startup profiling found initialization of Zod schemas, chat/Kody dependencies, database schema definitions, and garbage collection. A controlled build temporarily omitted the StreamObject, WorkspaceSync, Conversation and TanChatWorkflow exports from the shared entrypoint. Source exports were restored immediately after building. + +| Local comparison | Full Worker | Chat exports omitted | +| ------------------------------------ | ----------: | -------------------: | +| Sampled active module-startup CPU | 88.9ms | 23.9ms | +| Startup garbage collection | 13.8ms | 1.3ms | +| First docs request with warm content | 143ms | 191ms | +| Repeated docs request | 20ms | 19ms | + +These are single local samples, not production estimates or statistically stable benchmarks. The reduction in startup CPU did not produce an end-to-end improvement: schema initialization reappeared in the first-request CPU profile. Removing exports alone shifts work and is not a solution. + +The SSR import graph and the compiled router identify this eager dependency path: + +``` +src/router.tsx + -> src/routeTree.gen.ts + -> src/routes/api/chat/mcp/contract.ts + -> src/chat/server/reference-catalog-http.server.ts + -> src/chat/server/discovery-integrations/index.ts + -> src/chat/server/discovery-integrations/kody.ts + -> src/chat/server/discovery-integrations/kody-contract.ts + -> src/chat/server/kody-actions.ts + -> @babel/parser and @babel/types +``` + +The API route imports its handler at module scope. Kody actions imports Babel at module scope. The first docs request consequently initializes unrelated chat/API/compiler code while loading the router. Other routes and the Worker exports also reach chat dependencies, so changing just one route would not necessarily remove them. + +The next implementation should isolate those dependencies behind the routes/entrypoints that use them: lazy server-handler imports for unrelated API routes, and a separate chat Durable Object/workflow entrypoint if needed. Verify the generated static import graph and repeat cold-instance measurements, rather than assuming that smaller startup time means faster first responses. + +This identifies concrete unnecessary application work, not the entirety of the multi-second Cloudflare delay. Network/runtime startup remains outside the local request profile. The production source and full build have been restored, HTML caching remains disabled, and no experimental export removal is retained. + +Profiles from this run are saved locally at `/tmp/docs-startup-full.cpuprofile`, `/tmp/docs-startup-without-chat.cpuprofile`, `/tmp/docs-warm-content-first.cpuprofile`, and `/tmp/docs-warm-content-repeat.cpuprofile`. + +## TanChat isolation implementation, October 15 + +Chat API handlers and MCP OAuth handlers now import their implementations when invoked. Larger inline handlers live outside the generated route tree, under `src/chat/server/routes`. The chat access check, legacy chat redirects, appearance bootstrap, CSS URL, and shared builder loading UI also load on demand. The chat admin component no longer has an extra export that prevents automatic route splitting. The main navigation link disables hover preloading for TanChat. + +The four deployed Worker class names remain unchanged. Lightweight classes in `src/chat/server/entries.ts` defer their implementation imports until object construction or workflow execution. Durable Object initialization runs inside `blockConcurrencyWhile`, and explicit prototype methods preserve the RPC surface. No storage namespace, binding, or migration changes are needed. Scheduled work is imported only from the scheduled entrypoint. + +The production build now checks the emitted static chunk graph for both server and browser entrypoints, including the server router loaded by docs. Only two small synchronous routing-contract modules, `core/navigation.ts` and `core/workspace-panels.ts`, remain eager, along with the server's lightweight class declarations. No chat UI, services, MCP implementation, or Babel modules appeared in that graph. The build fails if another chat implementation module becomes eager. + +### Local validation + +- Production build passed its new isolation check. `pnpm test` passed all TypeScript configurations, lint, 560 unit tests, 2,270 chat tests, and 20 desktop tests. Five existing tests were skipped. +- Real local workerd initialized Conversation's SQLite state and returned `{running:false,pending:false}` through RPC. +- A local test subclass verified WorkspaceSync initialization. StreamObject created a stream, read its contents, and acquired a fork through RPC. +- A contract test compares every public RPC/lifecycle method against the lazy entrypoints. A separate test checks deferred imports, initialization, arguments, and return values. +- Browser docs rendering and unauthenticated chat navigation were checked against the local production build, using a generated local session secret. No production credentials or storage were used for the chat checks. + +| Local sample | Before isolation | After isolation | +| -------------------------------------- | ---------------: | --------------: | +| Sampled active module-startup CPU | 88.9ms | 22.7ms | +| Startup garbage collection | 13.8ms | 0ms sampled | +| First docs request, warm content, TTFB | 143ms | 171ms | +| Repeated docs request, TTFB | 20ms | 36ms | + +These are individual local profiles, not a controlled performance distribution. The startup sample improved by about 74%, but there is no demonstrated end-to-end TTFB improvement yet. The first-request profile no longer shows the chat/compiler initialization path; remaining work includes routing/rendering, shared partners/blog modules, and content-cache operations. Both measured docs requests reported no database use and no origin content fetch. + +The Worker still contains the lazy chat chunks in its upload, about 26MB uncompressed. This change reduces what initializes for unrelated requests, not total upload size. Production cold-instance measurements are still needed. At the end of the local validation, this isolation change had not been deployed. Complete HTML caching remains disabled. + +Profiles: `/tmp/docs-startup-isolated-chat.cpuprofile`, `/tmp/docs-isolated-chat-first.cpuprofile`, and `/tmp/docs-isolated-chat-repeat.cpuprofile`. Raw client timings: `/tmp/docs-isolated-chat-requests.jsonl`. + +## Production deployment and measurements, October 15 + +Deployed the tested build to `tanstack-com` at 20:41 UTC. Previous version: `9440f136-1e14-4cea-9583-830df3dae49c`. New version: `3b554871-c23c-4825-9bb4-f9bc27dc5466`. Deployment kept existing variables and secrets. Durable Object bindings/namespaces, database schema, and cache configuration did not change. No CDN purge was needed. The deployed changes include the earlier docs manifest/content-cache/header fixes and tracing as well as TanChat isolation. + +Cloudflare reported **36ms Worker startup**. The earlier isolated preview reported 345ms, which is a different deployment context, not a controlled production before/after startup benchmark. + +Measured six requests immediately before deployment, then 28 afterward from the same client through DFW. Requests used separate curl processes/connections and a one-second pause. Cloudflare tail records were joined by ray ID to obtain the actual serving version, CPU, wall time, and application timing logs. Five requests during rollout reached the previous version and are excluded from new-version statistics. All 12 requests in the final run reached the new version. + +| Request group | Samples | Median TTFB | TTFB range | Median platform CPU | Median platform wall | +| ------------------------------------------- | ------: | ----------: | ------------: | ------------------: | -------------------: | +| Before deployment, first three requests | 3 | 2,555ms | 1,846–2,914ms | 174ms | 451ms | +| Before deployment, last three requests | 3 | 305ms | 258–346ms | 93ms | 122ms | +| New version, first docs request on instance | 9 | 1,393ms | 297–3,271ms | 165ms | 362ms | +| New version, reused docs instance | 14 | 240ms | 214–440ms | 56ms | 77ms | + +The old version lacked the instance counter, so its first/last groups are observations of request order, not verified cold/warm labels. The new first-docs counter also does not prove that no other route already initialized the instance. These small samples suggest a warm-path improvement, but do not establish production percentiles or a causal percentage improvement from TanChat isolation alone. + +All 23 measured new-version responses returned 200 and `private, no-store`. Every corresponding docs timing record reported `databaseUsed: false`. On the 3,271ms outlier, Cloudflare's response header reported `cfEdge=2467ms` and `cfWorker=673ms`. Much of that wait lies outside the reported Worker duration. This does not identify which edge/platform operation caused it. The multi-second first-request problem remains. + +Read-only smoke checks passed: `/chat` redirected to `/chat-access` and returned 200; `/api/chat/bootstrap` returned the expected unauthenticated 401; `/auth/mcp/client-metadata` returned 200. Observed exception messages during the deployment window were network disconnects, with no module-initialization exception observed. Signed-in chat interactions were not exercised in production. + +Full request measurements and phase logs are in `docs-production-performance-measurements.json`. No follow-up code change was deployed after these measurements. + +## Detailed cold-request investigation + +The follow-up isolated the dominant delay to pre-handler preparation of the complete uploaded Worker, including code never imported by the request. See [the investigation](./worker-cold-start-investigation.md) and [220 probe measurements](./worker-cold-start-measurements.json). No additional production deployment was performed. diff --git a/docs/docs-production-performance-measurements.json b/docs/docs-production-performance-measurements.json new file mode 100644 index 000000000..81391475f --- /dev/null +++ b/docs/docs-production-performance-measurements.json @@ -0,0 +1,3188 @@ +{ + "measured_at_utc": "2026-10-15T20:40:09+00:00 through 2026-10-15T20:42:31+00:00", + "url": "https://tanstack.com/router/latest/docs/routing/routing-concepts", + "region": "DFW", + "previous_version": "9440f136-1e14-4cea-9583-830df3dae49c", + "deployed_version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "deployment_started_at_utc": "2026-10-15T20:41:02Z", + "reported_startup_ms": 36, + "before": [ + { + "request": 1, + "status": 200, + "ttfb_ms": 2914.0, + "total_ms": 3045.6, + "tls_ms": 165.9, + "ray": "a4b1a3106836f887-DFW", + "server_timing": "cfEdge;dur=1436,cfOrigin;dur=0,cfWorker;dur=1271", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:40:09 GMT", + "cpu_ms": 222, + "wall_ms": 2017, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 2, + "status": 200, + "ttfb_ms": 2555.4, + "total_ms": 2674.9, + "tls_ms": 79.1, + "ray": "a4b1a3294cf78c20-DFW", + "server_timing": "cfEdge;dur=1985,cfOrigin;dur=0,cfWorker;dur=448", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:40:12 GMT", + "cpu_ms": 174, + "wall_ms": 451, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 3, + "status": 200, + "ttfb_ms": 1846.0, + "total_ms": 1967.7, + "tls_ms": 85.4, + "ray": "a4b1a3406f2de7ff-DFW", + "server_timing": "cfEdge;dur=1314,cfOrigin;dur=0,cfWorker;dur=405", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:40:15 GMT", + "cpu_ms": 153, + "wall_ms": 409, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 4, + "status": 200, + "ttfb_ms": 345.6, + "total_ms": 463.5, + "tls_ms": 79.1, + "ray": "a4b1a3530c225e57-DFW", + "server_timing": "cfEdge;dur=30,cfOrigin;dur=0,cfWorker;dur=194", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:40:17 GMT", + "cpu_ms": 136, + "wall_ms": 196, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 5, + "status": 200, + "ttfb_ms": 257.5, + "total_ms": 386.8, + "tls_ms": 80.0, + "ray": "a4b1a35c4fb786ee-DFW", + "server_timing": "cfEdge;dur=27,cfOrigin;dur=0,cfWorker;dur=106", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:40:18 GMT", + "cpu_ms": 76, + "wall_ms": 106, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 6, + "status": 200, + "ttfb_ms": 305.3, + "total_ms": 429.5, + "tls_ms": 82.8, + "ray": "a4b1a3650cbfa927-DFW", + "server_timing": "cfEdge;dur=53,cfOrigin;dur=0,cfWorker;dur=120", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:40:20 GMT", + "cpu_ms": 93, + "wall_ms": 122, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + } + ], + "during_rollout": [ + { + "request": 1, + "status": 200, + "ttfb_ms": 2046.1, + "total_ms": 2167.8, + "tls_ms": 80.0, + "ray": "a4b1a4ad6db4a91e-DFW", + "server_timing": "cfEdge;dur=1377,cfOrigin;dur=0,cfWorker;dur=546", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:14 GMT", + "cpu_ms": 247, + "wall_ms": 550, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "13eb6168-600f-44ed-8a81-43632a34383d", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a4ad6db4a91e", + "status": 200, + "headersReadyMs": 297, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 291 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 291 + }, + { + "name": "content_edge_read", + "startMs": 291, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 297 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "13eb6168-600f-44ed-8a81-43632a34383d", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a4ad6db4a91e", + "elapsedMs": 297, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 291 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 291 + }, + { + "name": "content_edge_read", + "startMs": 291, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 297 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "13eb6168-600f-44ed-8a81-43632a34383d", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a4ad6db4a91e", + "elapsedMs": 297, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 291 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 291 + }, + { + "name": "content_edge_read", + "startMs": 291, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 297 + } + ] + } + ] + }, + { + "request": 2, + "status": 200, + "ttfb_ms": 2343.4, + "total_ms": 2462.2, + "tls_ms": 81.7, + "ray": "a4b1a4c15da114ef-DFW", + "server_timing": "cfEdge;dur=1794,cfOrigin;dur=0,cfWorker;dur=424", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:17 GMT", + "cpu_ms": 159, + "wall_ms": 430, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 3, + "status": 200, + "ttfb_ms": 2640.6, + "total_ms": 2761.0, + "tls_ms": 105.3, + "ray": "a4b1a4d73c384780-DFW", + "server_timing": "cfEdge;dur=1691,cfOrigin;dur=0,cfWorker;dur=795", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:21 GMT", + "cpu_ms": 428, + "wall_ms": 803, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "4b1da96b-ffe6-4c92-9ceb-572b0aa28c5e", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a4d73c384780", + "status": 200, + "headersReadyMs": 382, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 370 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 372 + }, + { + "name": "content_edge_read", + "startMs": 372, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 382 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "4b1da96b-ffe6-4c92-9ceb-572b0aa28c5e", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a4d73c384780", + "elapsedMs": 382, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 370 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 372 + }, + { + "name": "content_edge_read", + "startMs": 372, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 382 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "4b1da96b-ffe6-4c92-9ceb-572b0aa28c5e", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a4d73c384780", + "elapsedMs": 382, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 370 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 372 + }, + { + "name": "content_edge_read", + "startMs": 372, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 382 + } + ] + } + ] + }, + { + "request": 4, + "status": 200, + "ttfb_ms": 1963.5, + "total_ms": 2082.4, + "tls_ms": 86.8, + "ray": "a4b1a4eebd86f070-DFW", + "server_timing": "cfEdge;dur=1283,cfOrigin;dur=0,cfWorker;dur=550", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:24 GMT", + "cpu_ms": 302, + "wall_ms": 556, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 5, + "status": 200, + "ttfb_ms": 2901.2, + "total_ms": 3022.1, + "tls_ms": 109.0, + "ray": "a4b1a5023b59b1c9-DFW", + "server_timing": "cfEdge;dur=2270,cfOrigin;dur=0,cfWorker;dur=478", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:28 GMT", + "cpu_ms": 175, + "wall_ms": 483, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 6, + "status": 200, + "ttfb_ms": 1383.1, + "total_ms": 1504.1, + "tls_ms": 82.4, + "ray": "a4b1a51b5d0d9a79-DFW", + "server_timing": "cfEdge;dur=926,cfOrigin;dur=0,cfWorker;dur=331", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:31 GMT", + "cpu_ms": 147, + "wall_ms": 333, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a51b5d0d9a79", + "status": 200, + "headersReadyMs": 183, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 178 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 179 + }, + { + "name": "content_edge_read", + "startMs": 178, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 183 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a51b5d0d9a79", + "elapsedMs": 183, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 178 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 179 + }, + { + "name": "content_edge_read", + "startMs": 178, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 183 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a51b5d0d9a79", + "elapsedMs": 183, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 178 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 179 + }, + { + "name": "content_edge_read", + "startMs": 178, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 183 + } + ] + } + ] + }, + { + "request": 7, + "status": 200, + "ttfb_ms": 238.6, + "total_ms": 356.9, + "tls_ms": 81.3, + "ray": "a4b1a52b0eacee9e-DFW", + "server_timing": "cfEdge;dur=39,cfOrigin;dur=0,cfWorker;dur=72", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:32 GMT", + "cpu_ms": 57, + "wall_ms": 73, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 3, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a52b0eacee9e", + "status": 200, + "headersReadyMs": 15, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 10 + }, + { + "name": "content_edge_read", + "startMs": 10, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 15 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 3, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a52b0eacee9e", + "elapsedMs": 15, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 10 + }, + { + "name": "content_edge_read", + "startMs": 10, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 15 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 3, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a52b0eacee9e", + "elapsedMs": 15, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 10 + }, + { + "name": "content_edge_read", + "startMs": 10, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 15 + } + ] + } + ] + }, + { + "request": 8, + "status": 200, + "ttfb_ms": 215.5, + "total_ms": 332.7, + "tls_ms": 79.0, + "ray": "a4b1a5339937863c-DFW", + "server_timing": "cfEdge;dur=30,cfOrigin;dur=0,cfWorker;dur=65", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:34 GMT", + "cpu_ms": 54, + "wall_ms": 66, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 2, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5339937863c", + "status": 200, + "headersReadyMs": 12, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 6, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 12 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 2, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5339937863c", + "elapsedMs": 12, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 6, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 12 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 2, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5339937863c", + "elapsedMs": 12, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 6, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 12 + } + ] + } + ] + }, + { + "request": 9, + "status": 200, + "ttfb_ms": 326.9, + "total_ms": 449.4, + "tls_ms": 83.2, + "ray": "a4b1a53c091fc437-DFW", + "server_timing": "cfEdge;dur=19,cfOrigin;dur=0,cfWorker;dur=180", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:35 GMT", + "cpu_ms": 123, + "wall_ms": 181, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "4b9f0cbd-4094-45fa-88a2-40cb7822ea9d", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a53c091fc437", + "status": 200, + "headersReadyMs": 43, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 33 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 34 + }, + { + "name": "content_edge_read", + "startMs": 33, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 43 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "4b9f0cbd-4094-45fa-88a2-40cb7822ea9d", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a53c091fc437", + "elapsedMs": 43, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 33 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 34 + }, + { + "name": "content_edge_read", + "startMs": 33, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 43 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "4b9f0cbd-4094-45fa-88a2-40cb7822ea9d", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a53c091fc437", + "elapsedMs": 43, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 33 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 34 + }, + { + "name": "content_edge_read", + "startMs": 33, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 43 + } + ] + } + ] + }, + { + "request": 10, + "status": 200, + "ttfb_ms": 242.2, + "total_ms": 358.2, + "tls_ms": 99.6, + "ray": "a4b1a5455a6e2ba1-DFW", + "server_timing": "cfEdge;dur=37,cfOrigin;dur=0,cfWorker;dur=60", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:36 GMT", + "cpu_ms": 48, + "wall_ms": 61, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 4, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5455a6e2ba1", + "status": 200, + "headersReadyMs": 11, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 7, + "durationMs": 4 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 11 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 4, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5455a6e2ba1", + "elapsedMs": 11, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 7, + "durationMs": 4 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 11 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 4, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5455a6e2ba1", + "elapsedMs": 11, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 7, + "durationMs": 4 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 11 + } + ] + } + ] + }, + { + "request": 11, + "status": 200, + "ttfb_ms": 297.4, + "total_ms": 423.3, + "tls_ms": 81.5, + "ray": "a4b1a54dd863b66c-DFW", + "server_timing": "cfEdge;dur=38,cfOrigin;dur=0,cfWorker;dur=134", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:38 GMT", + "cpu_ms": 104, + "wall_ms": 135, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "d94a23be-f6aa-4d62-9705-df14cc3b59db", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a54dd863b66c", + "status": 200, + "headersReadyMs": 24, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 15 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 15 + }, + { + "name": "content_edge_read", + "startMs": 15, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 24 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "d94a23be-f6aa-4d62-9705-df14cc3b59db", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a54dd863b66c", + "elapsedMs": 24, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 15 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 15 + }, + { + "name": "content_edge_read", + "startMs": 15, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 24 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "d94a23be-f6aa-4d62-9705-df14cc3b59db", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a54dd863b66c", + "elapsedMs": 24, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 15 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 15 + }, + { + "name": "content_edge_read", + "startMs": 15, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 24 + } + ] + } + ] + }, + { + "request": 12, + "status": 200, + "ttfb_ms": 2861.6, + "total_ms": 2992.5, + "tls_ms": 89.6, + "ray": "a4b1a556d8546c6c-DFW", + "server_timing": "cfEdge;dur=2015,cfOrigin;dur=0,cfWorker;dur=711", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:42 GMT", + "cpu_ms": 307, + "wall_ms": 721, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 13, + "status": 200, + "ttfb_ms": 1586.5, + "total_ms": 2141.4, + "tls_ms": 81.2, + "ray": "a4b1a56fdf42c514-DFW", + "server_timing": "cfEdge;dur=909,cfOrigin;dur=0,cfWorker;dur=531", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:45 GMT", + "cpu_ms": 206, + "wall_ms": 535, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "3f5f2043-2489-4def-9b91-f37cc5fb244c", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a56fdf42c514", + "status": 200, + "headersReadyMs": 328, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 320 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 320 + }, + { + "name": "content_edge_read", + "startMs": 320, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 328 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "3f5f2043-2489-4def-9b91-f37cc5fb244c", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a56fdf42c514", + "elapsedMs": 328, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 320 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 320 + }, + { + "name": "content_edge_read", + "startMs": 320, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 328 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "3f5f2043-2489-4def-9b91-f37cc5fb244c", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a56fdf42c514", + "elapsedMs": 328, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 320 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 320 + }, + { + "name": "content_edge_read", + "startMs": 320, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 328 + } + ] + } + ] + }, + { + "request": 14, + "status": 200, + "ttfb_ms": 2910.9, + "total_ms": 3125.2, + "tls_ms": 81.6, + "ray": "a4b1a5838ef1f088-DFW", + "server_timing": "cfEdge;dur=2123,cfOrigin;dur=0,cfWorker;dur=659", + "cache_control": "public, max-age=0, must-revalidate", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:49 GMT", + "cpu_ms": 310, + "wall_ms": 665, + "version": "9440f136-1e14-4cea-9583-830df3dae49c", + "outcome": "ok" + }, + { + "request": 15, + "status": 200, + "ttfb_ms": 439.8, + "total_ms": 563.1, + "tls_ms": 93.2, + "ray": "a4b1a59d89f70af8-DFW", + "server_timing": "cfEdge;dur=221,cfOrigin;dur=0,cfWorker;dur=75", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:51 GMT", + "cpu_ms": 55, + "wall_ms": 76, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 5, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a59d89f70af8", + "status": 200, + "headersReadyMs": 17, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 17 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 5, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a59d89f70af8", + "elapsedMs": 17, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 17 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 5, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a59d89f70af8", + "elapsedMs": 17, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 17 + } + ] + } + ] + }, + { + "request": 16, + "status": 200, + "ttfb_ms": 1353.9, + "total_ms": 1472.4, + "tls_ms": 82.4, + "ray": "a4b1a5a75b4df07d-DFW", + "server_timing": "cfEdge;dur=905,cfOrigin;dur=0,cfWorker;dur=319", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:41:53 GMT", + "cpu_ms": 159, + "wall_ms": 322, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "2cd6c3cf-4089-4604-9783-2cf642580036", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5a75b4df07d", + "status": 200, + "headersReadyMs": 154, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 151 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 151 + }, + { + "name": "content_edge_read", + "startMs": 151, + "durationMs": 3 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 154 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "2cd6c3cf-4089-4604-9783-2cf642580036", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5a75b4df07d", + "elapsedMs": 154, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 151 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 151 + }, + { + "name": "content_edge_read", + "startMs": 151, + "durationMs": 3 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 154 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "2cd6c3cf-4089-4604-9783-2cf642580036", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a5a75b4df07d", + "elapsedMs": 154, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 151 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 151 + }, + { + "name": "content_edge_read", + "startMs": 151, + "durationMs": 3 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 154 + } + ] + } + ] + } + ], + "after_rollout": [ + { + "request": 1, + "status": 200, + "ttfb_ms": 362.0, + "total_ms": 478.0, + "tls_ms": 81.8, + "ray": "a4b1a6206e2f6c38-DFW", + "server_timing": "cfEdge;dur=65,cfOrigin;dur=0,cfWorker;dur=165", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:12 GMT", + "cpu_ms": 130, + "wall_ms": 167, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "491c8962-14e6-4f24-b558-698b6566fbef", + "docsRequest": 2, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6206e2f6c38", + "status": 200, + "headersReadyMs": 22, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 13, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 22 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "491c8962-14e6-4f24-b558-698b6566fbef", + "docsRequest": 2, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6206e2f6c38", + "elapsedMs": 22, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 13, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 22 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "491c8962-14e6-4f24-b558-698b6566fbef", + "docsRequest": 2, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6206e2f6c38", + "elapsedMs": 22, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 13, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 22 + } + ] + } + ] + }, + { + "request": 2, + "status": 200, + "ttfb_ms": 232.1, + "total_ms": 348.8, + "tls_ms": 85.1, + "ray": "a4b1a629c89724e9-DFW", + "server_timing": "cfEdge;dur=14,cfOrigin;dur=0,cfWorker;dur=85", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:13 GMT", + "cpu_ms": 59, + "wall_ms": 86, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 6, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a629c89724e9", + "status": 200, + "headersReadyMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 10 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 11, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 6, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a629c89724e9", + "elapsedMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 10 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 11, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 6, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a629c89724e9", + "elapsedMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 10 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 11, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + } + ] + }, + { + "request": 3, + "status": 200, + "ttfb_ms": 241.9, + "total_ms": 361.0, + "tls_ms": 83.7, + "ray": "a4b1a6324b3a0dc1-DFW", + "server_timing": "cfEdge;dur=43,cfOrigin;dur=0,cfWorker;dur=71", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:14 GMT", + "cpu_ms": 49, + "wall_ms": 72, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 7, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6324b3a0dc1", + "status": 200, + "headersReadyMs": 23, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 13, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 23 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 7, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6324b3a0dc1", + "elapsedMs": 23, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 13, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 23 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 7, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6324b3a0dc1", + "elapsedMs": 23, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 13 + }, + { + "name": "content_edge_read", + "startMs": 13, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 23 + } + ] + } + ] + }, + { + "request": 4, + "status": 200, + "ttfb_ms": 213.9, + "total_ms": 331.1, + "tls_ms": 88.5, + "ray": "a4b1a63b0a03e792-DFW", + "server_timing": "cfEdge;dur=13,cfOrigin;dur=0,cfWorker;dur=69", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:16 GMT", + "cpu_ms": 49, + "wall_ms": 70, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 3, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a63b0a03e792", + "status": 200, + "headersReadyMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 3, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a63b0a03e792", + "elapsedMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 3, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a63b0a03e792", + "elapsedMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + } + ] + }, + { + "request": 5, + "status": 200, + "ttfb_ms": 3271.0, + "total_ms": 3397.0, + "tls_ms": 84.2, + "ray": "a4b1a6436fc13ad3-DFW", + "server_timing": "cfEdge;dur=2467,cfOrigin;dur=0,cfWorker;dur=673", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:20 GMT", + "cpu_ms": 370, + "wall_ms": 681, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "c0b6b4e8-1e60-4167-bb47-1f00c605143e", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6436fc13ad3", + "status": 200, + "headersReadyMs": 317, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 313 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 317 + }, + { + "name": "content_edge_read", + "startMs": 313, + "durationMs": 4 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 317 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "c0b6b4e8-1e60-4167-bb47-1f00c605143e", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6436fc13ad3", + "elapsedMs": 317, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 313 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 317 + }, + { + "name": "content_edge_read", + "startMs": 313, + "durationMs": 4 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 317 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "c0b6b4e8-1e60-4167-bb47-1f00c605143e", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6436fc13ad3", + "elapsedMs": 317, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 313 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 317 + }, + { + "name": "content_edge_read", + "startMs": 313, + "durationMs": 4 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 317 + } + ] + } + ] + }, + { + "request": 6, + "status": 200, + "ttfb_ms": 231.8, + "total_ms": 355.4, + "tls_ms": 85.5, + "ray": "a4b1a65f0858f0c2-DFW", + "server_timing": "cfEdge;dur=26,cfOrigin;dur=0,cfWorker;dur=77", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:22 GMT", + "cpu_ms": 55, + "wall_ms": 78, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "2cd6c3cf-4089-4604-9783-2cf642580036", + "docsRequest": 2, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a65f0858f0c2", + "status": 200, + "headersReadyMs": 12, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 7, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 12 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "2cd6c3cf-4089-4604-9783-2cf642580036", + "docsRequest": 2, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a65f0858f0c2", + "elapsedMs": 12, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 7, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 12 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "2cd6c3cf-4089-4604-9783-2cf642580036", + "docsRequest": 2, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a65f0858f0c2", + "elapsedMs": 12, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 7 + }, + { + "name": "content_edge_read", + "startMs": 7, + "durationMs": 5 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 12 + } + ] + } + ] + }, + { + "request": 7, + "status": 200, + "ttfb_ms": 319.2, + "total_ms": 442.7, + "tls_ms": 92.6, + "ray": "a4b1a667d81d2ca4-DFW", + "server_timing": "cfEdge;dur=32,cfOrigin;dur=0,cfWorker;dur=118", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:23 GMT", + "cpu_ms": 98, + "wall_ms": 119, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 4, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a667d81d2ca4", + "status": 200, + "headersReadyMs": 16, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 5 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 8, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 16 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 4, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a667d81d2ca4", + "elapsedMs": 16, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 5 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 8, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 16 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "ceee50a1-bdee-4077-a79a-4d5707f53e98", + "docsRequest": 4, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a667d81d2ca4", + "elapsedMs": 16, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 5 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 8, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 16 + } + ] + } + ] + }, + { + "request": 8, + "status": 200, + "ttfb_ms": 278.6, + "total_ms": 397.5, + "tls_ms": 79.7, + "ray": "a4b1a670cdd8533a-DFW", + "server_timing": "cfEdge;dur=26,cfOrigin;dur=0,cfWorker;dur=130", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:24 GMT", + "cpu_ms": 106, + "wall_ms": 133, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "13eb6168-600f-44ed-8a81-43632a34383d", + "docsRequest": 5, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a670cdd8533a", + "status": 200, + "headersReadyMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 12 + }, + { + "name": "content_edge_read", + "startMs": 12, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "13eb6168-600f-44ed-8a81-43632a34383d", + "docsRequest": 5, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a670cdd8533a", + "elapsedMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 12 + }, + { + "name": "content_edge_read", + "startMs": 12, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "13eb6168-600f-44ed-8a81-43632a34383d", + "docsRequest": 5, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a670cdd8533a", + "elapsedMs": 18, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 12 + }, + { + "name": "content_edge_read", + "startMs": 12, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 18 + } + ] + } + ] + }, + { + "request": 9, + "status": 200, + "ttfb_ms": 236.8, + "total_ms": 367.0, + "tls_ms": 83.9, + "ray": "a4b1a67998bad7a0-DFW", + "server_timing": "cfEdge;dur=23,cfOrigin;dur=0,cfWorker;dur=89", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:26 GMT", + "cpu_ms": 70, + "wall_ms": 90, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "3f5f2043-2489-4def-9b91-f37cc5fb244c", + "docsRequest": 2, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a67998bad7a0", + "status": 200, + "headersReadyMs": 20, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 11 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 20 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "3f5f2043-2489-4def-9b91-f37cc5fb244c", + "docsRequest": 2, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a67998bad7a0", + "elapsedMs": 20, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 11 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 20 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "3f5f2043-2489-4def-9b91-f37cc5fb244c", + "docsRequest": 2, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a67998bad7a0", + "elapsedMs": 20, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 9 + }, + { + "name": "content_edge_read", + "startMs": 9, + "durationMs": 11 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 20 + } + ] + } + ] + }, + { + "request": 10, + "status": 200, + "ttfb_ms": 231.2, + "total_ms": 397.9, + "tls_ms": 82.4, + "ray": "a4b1a6823d28a270-DFW", + "server_timing": "cfEdge;dur=36,cfOrigin;dur=0,cfWorker;dur=67", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:27 GMT", + "cpu_ms": 52, + "wall_ms": 68, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "4b9f0cbd-4094-45fa-88a2-40cb7822ea9d", + "docsRequest": 2, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6823d28a270", + "status": 200, + "headersReadyMs": 16, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 8, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 16 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "4b9f0cbd-4094-45fa-88a2-40cb7822ea9d", + "docsRequest": 2, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6823d28a270", + "elapsedMs": 16, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 8, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 16 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "4b9f0cbd-4094-45fa-88a2-40cb7822ea9d", + "docsRequest": 2, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a6823d28a270", + "elapsedMs": 16, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 8 + }, + { + "name": "content_edge_read", + "startMs": 8, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 16 + } + ] + } + ] + }, + { + "request": 11, + "status": 200, + "ttfb_ms": 1392.7, + "total_ms": 1510.5, + "tls_ms": 79.1, + "ray": "a4b1a68b1cc769f5-DFW", + "server_timing": "cfEdge;dur=905,cfOrigin;dur=0,cfWorker;dur=360", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:30 GMT", + "cpu_ms": 165, + "wall_ms": 362, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "e3c88c49-0501-4898-b6a5-cf830552fc92", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a68b1cc769f5", + "status": 200, + "headersReadyMs": 197, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 190 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 190 + }, + { + "name": "content_edge_read", + "startMs": 190, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 197 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "e3c88c49-0501-4898-b6a5-cf830552fc92", + "docsRequest": 1, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a68b1cc769f5", + "elapsedMs": 197, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 190 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 190 + }, + { + "name": "content_edge_read", + "startMs": 190, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 197 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "e3c88c49-0501-4898-b6a5-cf830552fc92", + "docsRequest": 1, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a68b1cc769f5", + "elapsedMs": 197, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 190 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 190 + }, + { + "name": "content_edge_read", + "startMs": 190, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 197 + } + ] + } + ] + }, + { + "request": 12, + "status": 200, + "ttfb_ms": 271.7, + "total_ms": 392.4, + "tls_ms": 90.8, + "ray": "a4b1a69afceb1974-DFW", + "server_timing": "cfEdge;dur=29,cfOrigin;dur=0,cfWorker;dur=99", + "cache_control": "private, no-store", + "cf_cache_status": null, + "date": "Thu, 15 Oct 2026 20:42:31 GMT", + "cpu_ms": 71, + "wall_ms": 100, + "version": "3b554871-c23c-4825-9bb4-f9bc27dc5466", + "outcome": "ok", + "docs_timings": [ + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 8, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a69afceb1974", + "status": 200, + "headersReadyMs": 22, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 11, + "durationMs": 11 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 22 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 8, + "phase": "first_body_byte", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a69afceb1974", + "elapsedMs": 22, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 11, + "durationMs": 11 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 22 + } + ] + }, + { + "logType": "docs_timing", + "workerInstance": "0bc0619a-83d0-473f-a9fb-03e1973224d5", + "docsRequest": 8, + "phase": "body_complete", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1a69afceb1974", + "elapsedMs": 22, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 11, + "durationMs": 11 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 22 + } + ] + } + ] + } + ], + "notes": [ + "Separate curl process and TLS connection per request, Accept: text/html, one-second pause between requests.", + "First docs request is an observed per-instance counter, not proof of a platform cold start.", + "Exclude five previous-version responses from new-version statistics.", + "Application timing clocks advance with I/O; use platform CPU/wall counters to complement them.", + "This deployment includes the preceding docs fixes and tracing as well as TanChat isolation; results do not isolate any single change." + ] +} diff --git a/docs/worker-cold-start-investigation.md b/docs/worker-cold-start-investigation.md new file mode 100644 index 000000000..486bb1c4d --- /dev/null +++ b/docs/worker-cold-start-investigation.md @@ -0,0 +1,109 @@ +# What makes the first docs request slow + +The largest delay happens before the Worker fetch handler runs. It depends on the uploaded application code, even when that code is never imported or executed by the request. The remaining first-render cost is much smaller and includes real application CPU and content-cache reads. + +This explains why the TanChat import split helped runtime initialization without eliminating the multi-second spikes. The code stayed in the roughly 26,260 KiB Worker upload. Lazy imports control module execution, but do not remove the platform cost associated with carrying that upload. + +## A production request, broken down + +Production version `3b554871-c23c-4825-9bb4-f9bc27dc5466`, ray `a4b1a6436fc13ad3-DFW`: + +| Measurement | Time | +| --------------------------------- | ------: | +| Client time to first byte | 3,271ms | +| Connection/TLS setup | 84ms | +| Reported Cloudflare edge timing | 2,467ms | +| Reported Cloudflare Worker timing | 673ms | +| Tail invocation wall time | 681ms | +| Tail invocation CPU | 370ms | +| Application headers-ready clock | 317ms | + +Worker wall time includes CPU and waiting, so its CPU row must not be added again. After connection setup, roughly 2.5 seconds remain outside the recorded invocation, including network overhead. The small-worker controls below show that ordinary request/response transport accounts for tens of milliseconds in this environment, rather than the multi-second difference. + +The application recorded no database access, no origin content fetch, and no R2 read on this request. Its content came from the blob Cache API path. PostgreSQL and GitHub did not cause this spike. + +The application's 317ms measurement is not a second wall-clock measurement. Deployed Worker clocks advance with I/O and cannot accurately time CPU-only phases. Its two initial content reads ran in parallel. The `content_edge_read` label also includes reading the cached response body, not just `cache.match`. These spans locate work, but do not prove that the entire interval was storage-service latency. [Cloudflare timer behavior](https://developers.cloudflare.com/workers/runtime-apis/performance/) + +## Controlled proof of the pre-handler cost + +Created eight isolated Workers with no production routes, database, application secrets, Durable Objects, or scheduled jobs. The basic probe returns `ok`, records an instance ID and invocation counter, and performs no cache/storage/network operation on `/noop`. All comparisons below used DFW, separate curl connections, and a half-second pause between requests. + +The main timing removes connection setup: `time_starttransfer - time_pretransfer`. It still includes request/response network transit. "First" means the first observed handler invocation on that instance ID, which is a useful proxy rather than proof of a platform code-cache miss. + +| Upload/control | Upload KiB | First-instance samples | Median time after connection | Median invocation CPU / wall | +| --------------------------------------------------------------- | ---------: | ---------------------: | ---------------------------: | ---------------------------: | +| Minimal probe | 1.36 | 11 | 83ms | 0 / 1ms | +| Probe plus entire site upload, never imported | 26,263.71 | 6 | 1,114ms | 0 / 0ms | +| Probe plus static import of actual app, without rendering | 26,263.71 | 8 | 1,419ms | 0 / 0ms | +| Site JavaScript bytes as one inert Data module, excluding WASM | 22,615.06 | 13 | 315ms | 0 / 1ms | +| Entire site JavaScript, with empty diagnostic WASM | 22,617.00 | 16 | 897ms | 0 / 1ms | +| Similar bytes as one inert JavaScript string module | 23,239.25 | 20 | 527ms | 0 / 0ms | +| Similar string data split across 1,000 JavaScript modules | 23,256.81 | 14 | 441ms | 0 / 0.5ms | +| Actual app rebundled into one JS module, original WASM retained | 26,244.80 | 6 | 1,085ms | 0 / 0.5ms | + +The never-imported row uses the second run, after initial workers.dev routing failures. Other controls pool their successful runs. Non-200 platform responses remain in the raw measurements but are excluded from latency summaries. Some newly created probes returned 1042 or 1104, including probes with no application execution. These were not production responses. The later repeat runs for the single-string and empty-WASM controls returned 200 throughout. + +The same large never-imported Worker had a reused-instance median of 76ms after connection, close to the minimal Worker's 67ms. The roughly one-second cold penalty disappears when the instance is reused, despite identical response code and no data access. + +### What the controls establish + +- The production application upload itself causes substantial work before fetch dispatch. The probe can reproduce seconds of latency while the recorded invocation takes 0–2ms. +- Byte volume matters: even inert data adds cold latency. That Data control excludes the original WASM and is not an exact byte-for-byte match of the complete upload. +- Actual application JavaScript has more cold cost than similarly sized inert string/data controls. The platform is doing more than transmitting an equal number of bytes. +- The original OG WebAssembly payload is a contributor, but the JavaScript-only control remains slow. Replacing WASM is only a diagnostic experiment, not a usable application change. +- Module count alone does not explain the result. A thousand simple string modules were not slower than one large string module in these samples. Merging the actual app also left about a second of pre-handler delay. + +These are small sequential samples on different Workers, not randomized additive estimates. The differences should not be summed into an exact per-feature budget. What is directly verified is the dependence on the complete uploaded code and the timing boundary before the application's handler. The experiments cannot separate Cloudflare's internal script retrieval, decoding, compilation, or isolate preparation into individual timers. + +The reported deployment startup times ranged from 1–3ms for the synthetic probes to 35ms for the imported app and 87ms for the rebundle. Those figures did not predict the observed first-request delay. Treat deployment startup validation and real cold request latency as different measurements. + +## What is in that upload + +Source maps identify these large chunks. Sizes are uncompressed KiB from the source-mapped build, with a few extra bytes for map references: + +| Chunk | KiB | Source | +| --------------------------- | ----: | ----------------------------------------------------------------------------------------------------- | +| Iconography browser | 3,715 | `src/routes/ds.iconography.lazy.tsx`, generated Phosphor registry, 2,592 source modules | +| OG renderer WASM | 3,647 | Takumi, reached from `src/server/og/generate.server.ts` | +| AI starter template | 1,855 | `@tanstack/create/dist/generated/worker/frameworks/react/add-ons/ai.js`, generated project files/data | +| TanChat app UI | 1,204 | `src/chat/components/App.tsx` and its component dependencies | +| Chat model runtime | 990 | `src/chat/server/run-models.ts`, provider SDKs and attachment handling | +| Blog collection | 822 | generated `allPosts`, `src/utils/blog.ts`, and formatting | +| Conversation implementation | 673 | `src/chat/server/conversation.ts` and related code | +| Discovery integrations | 578 | chat discovery/integration dependencies | + +Most of this code is unrelated to rendering a docs page. The previous TanChat change correctly removed its implementation from the docs runtime import path, but the lazy chunks still ship as modules in the same Worker. + +## The first docs render, after the platform loads the Worker + +The actual-app probe returned valid docs pages. Three requests that both created a probe instance and rendered docs used 306–346ms of invocation CPU and 559–677ms of invocation wall time. The separate pre-handler delay was still present. + +On the local production build with warm content storage, the first docs response took 135ms to headers, versus 24ms on repeat. Source-mapped CPU samples located work in: + +- cached JSON parsing, especially `readStoredValue` in `src/utils/github-content-cache.server.ts`; +- partner sorting through `comparePartnerIdentity` and its first `localeCompare` calls; +- loading the complete generated blog collection; +- route manifest construction and runtime module initialization; +- React rendering, TanStack Redact traversal, serialization, and Tailwind class merging. + +Examples of local sampled self time were about 8.5ms in cached JSON parsing, 7.4ms in partner comparison, and 5.4ms in blog module initialization. These samples identify functions, not production timings. The profiler also captures runtime/inspector work outside the HTTP interval, so its total sampled active time is not presented as request CPU. + +The standalone binding probes were much smaller than the worst pre-handler delay: three Cache API miss operations took 6–33ms of recorded wall time, and three R2 HEAD miss operations took 111–130ms. These probes are not identical to reading populated docs cache bodies and cannot replace native traces for the actual hit path. + +## What would address the cause + +The next optimization needs to remove bytes and code from the docs-serving Worker upload. More dynamic imports inside the same upload do not address the demonstrated platform cost. + +The largest candidates are moving the client-only icon catalog out of server output, serving generated project template data as assets, and putting OG generation and TanChat behind separate Worker boundaries. Splitting TanChat also needs a deliberate Durable Object namespace plan. Those are proposed follow-up changes, not deployed fixes from this investigation. + +The monolithic rebundle is not recommended: its pre-handler delay persisted and its first docs renders used more CPU, reaching 854ms in this small sample. + +For Cloudflare support, the exact remaining platform question is: which operations account for the gap between HTTP arrival and fetch dispatch when an unused 26MB code upload is attached? The minimal-vs-unimported reproduction avoids framework rendering and storage entirely. Exact internal attribution to code retrieval versus compilation requires platform telemetry beyond the exposed invocation records. + +## Trace access and evidence + +Production native Cloudflare tracing was disabled, although logs and the separate Sentry SDK were configured. Native tracing was enabled on the isolated probes. The native telemetry query endpoint returned 403 with the available credentials, so no native binding span breakdown is claimed. The production CPU profile API succeeded but selected an idle isolate and contained no useful execution samples. That profiler targets an already-loaded isolate and cannot directly profile the preceding load delay. [Cloudflare production profiling](https://developers.cloudflare.com/workers/observability/profiling-in-production/) + +`worker-cold-start-measurements.json` contains 220 probe request records, version IDs, platform CPU/wall measurements, source rankings, and local CPU attribution. `docs-production-performance-measurements.json` contains the production request above. Archived probe and measurement templates are in `worker-cold-start-probes`. + +No production code or cache settings were changed during this investigation. Temporary diagnostic Workers are removed after capture; cleanup results are recorded alongside the measurements. diff --git a/docs/worker-cold-start-measurements.json b/docs/worker-cold-start-measurements.json new file mode 100644 index 000000000..e4d594156 --- /dev/null +++ b/docs/worker-cold-start-measurements.json @@ -0,0 +1,8123 @@ +{ + "date": "2026-10-15", + "region": "DFW", + "method": "Separate curl connection per request, 0.5 second gaps. after_connection_ms = 1000 * (time_starttransfer - time_pretransfer). First means observed handler count 1 on a generated instance ID. Platform timings joined by cf-ray.", + "scope": "Isolated workers.dev probes, no production routes, application secrets, database, Durable Object bindings, or scheduled tasks. Existing docs content R2 bucket was bound for read probes and cached docs rendering.", + "controls": { + "minimal": "1.36 KiB probe only", + "tiny": "Same probe, but entire site module upload present and never imported", + "full": "Static import of actual application, same probe plus real docs", + "data": "JS bytes concatenated as one inert Data module; no actual site JS or WASM modules", + "no-wasm": "All site JavaScript retained, original compiled OG WASM replaced by empty valid WASM for diagnostic noop calls only", + "single-js": "JS bytes encoded as inert exported string in one module", + "many-js": "Same string data distributed over 1000 exported-string modules", + "monolith": "Actual app rebundled by esbuild into one JS app module, retaining original WASM, plus probe entry" + }, + "important_limits": [ + "First observed invocation is not proof of a platform code-cache miss.", + "Synthetic controls isolate factors but timings are not additive and were not randomized.", + "Non-200 platform responses are retained and excluded from latency summaries.", + "No-wasm control cannot render OG images and is not an application fix.", + "Monolithic rebundle was diagnostic only and was not deployed to production.", + "Reported startup validation time and invocation CPU/wall do not cover all observed pre-handler delay.", + "Native trace query API returned 403 with available credentials. Production native tracing was disabled; probe tracing was enabled.", + "Remote production profile succeeded but contained only root/idle samples, so it does not attribute cold loading." + ], + "workers": [ + { + "worker": "minimal", + "deployed_name": "tanstack-cold-probe-minimal", + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b", + "upload_kib": 1.36, + "gzip_kib": 0.57, + "reported_startup_ms": 2, + "runs": { + "initial": [ + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 202.1, + "total_ms": 204.6, + "tls_ms": 129.2, + "after_connection_ms": 72.8, + "ray": "a4b1ad6b9f70dc12-DFW", + "instance": "140c9fe5-7462-4d82-8a9b-a08772838d52", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 154.1, + "total_ms": 154.3, + "tls_ms": 86.2, + "after_connection_ms": 67.8, + "ray": "a4b1ad6fdc82e7af-DFW", + "instance": "4a65acb8-fa22-4c73-bb87-2d9283a70bf9", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 150.7, + "total_ms": 151.1, + "tls_ms": 83.2, + "after_connection_ms": 67.4, + "ray": "a4b1ad73f961fa54-DFW", + "instance": "3858ef92-e0b2-404c-9058-905563a1464c", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 149.9, + "total_ms": 150.3, + "tls_ms": 78.1, + "after_connection_ms": 71.8, + "ray": "a4b1ad781d583cd3-DFW", + "instance": "140c9fe5-7462-4d82-8a9b-a08772838d52", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 3, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 183.7, + "total_ms": 185.9, + "tls_ms": 79.8, + "after_connection_ms": 103.8, + "ray": "a4b1ad7c4f6b45e0-DFW", + "instance": "46b5d0c8-f4ec-4adf-bea8-dd5dc67f6edb", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 146.5, + "total_ms": 148.9, + "tls_ms": 82.2, + "after_connection_ms": 64.3, + "ray": "a4b1ad80ad545fd1-DFW", + "instance": "3858ef92-e0b2-404c-9058-905563a1464c", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 149.9, + "total_ms": 150.4, + "tls_ms": 79.8, + "after_connection_ms": 70.1, + "ray": "a4b1ad84d80c2fc2-DFW", + "instance": "4a65acb8-fa22-4c73-bb87-2d9283a70bf9", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 194.6, + "total_ms": 195.0, + "tls_ms": 79.9, + "after_connection_ms": 114.6, + "ray": "a4b1ad88f8abf02e-DFW", + "instance": "b7e7af54-9470-47d3-b1b0-2fc0fe275b62", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 145.4, + "total_ms": 145.8, + "tls_ms": 78.7, + "after_connection_ms": 66.7, + "ray": "a4b1ad8d6c849bde-DFW", + "instance": "140c9fe5-7462-4d82-8a9b-a08772838d52", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 151.1, + "total_ms": 151.4, + "tls_ms": 79.1, + "after_connection_ms": 71.9, + "ray": "a4b1ad918baaf06a-DFW", + "instance": "b7e7af54-9470-47d3-b1b0-2fc0fe275b62", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 163.5, + "total_ms": 165.8, + "tls_ms": 79.6, + "after_connection_ms": 83.8, + "ray": "a4b1ad95bfc46c55-DFW", + "instance": "048e5114-d442-4985-8f8f-b7b192e84db1", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 147.8, + "total_ms": 148.1, + "tls_ms": 81.7, + "after_connection_ms": 66.1, + "ray": "a4b1ad9a0a2dc05d-DFW", + "instance": "546e662b-64ff-456f-9ad7-4ce28c752276", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 262.7, + "total_ms": 265.1, + "tls_ms": 89.1, + "after_connection_ms": 173.6, + "ray": "a4b1ad9e2a2969f8-DFW", + "instance": "612cca99-a59c-4454-a7bd-d37d8f6f960d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 3, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 171.9, + "total_ms": 172.2, + "tls_ms": 83.9, + "after_connection_ms": 88.0, + "ray": "a4b1ada2f9a84796-DFW", + "instance": "44313d34-67d1-4ab1-bf0a-9be22bca4d11", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 176.0, + "total_ms": 178.3, + "tls_ms": 130.3, + "after_connection_ms": 45.6, + "ray": "a4b1ada79d5e45fe-DFW", + "instance": "4a65acb8-fa22-4c73-bb87-2d9283a70bf9", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 166.6, + "total_ms": 168.9, + "tls_ms": 83.9, + "after_connection_ms": 82.6, + "ray": "a4b1adabacb6e68b-DFW", + "instance": "403ed82b-38e9-4623-8856-74db379ce24b", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 134.8, + "total_ms": 137.2, + "tls_ms": 84.7, + "after_connection_ms": 50.1, + "ray": "a4b1adaffa1babf8-DFW", + "instance": "a28c01f9-72df-4373-b844-bb59aa4ab146", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 139.7, + "total_ms": 142.0, + "tls_ms": 79.3, + "after_connection_ms": 60.3, + "ray": "a4b1adb41ef80864-DFW", + "instance": "3858ef92-e0b2-404c-9058-905563a1464c", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 144.1, + "total_ms": 146.1, + "tls_ms": 80.0, + "after_connection_ms": 64.0, + "ray": "a4b1adb83cb6f35f-DFW", + "instance": "546e662b-64ff-456f-9ad7-4ce28c752276", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + }, + { + "worker": "minimal", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 148.8, + "total_ms": 149.1, + "tls_ms": 81.7, + "after_connection_ms": 67.0, + "ray": "a4b1adbc5ecb0364-DFW", + "instance": "3858ef92-e0b2-404c-9058-905563a1464c", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "994eeeda-9b1b-44aa-bd93-f11f5a7ca04b" + } + ] + } + }, + { + "worker": "tiny", + "deployed_name": "tanstack-cold-probe-tiny", + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1", + "upload_kib": 26263.71, + "gzip_kib": 8459.83, + "reported_startup_ms": 3, + "runs": { + "initial": [ + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 1045.3, + "total_ms": 1045.5, + "tls_ms": 732.7, + "ray": "a4b1abfaff64c55c-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 619.2, + "total_ms": 621.5, + "tls_ms": 428.6, + "ray": "a4b1ac01dc573470-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2867.0, + "total_ms": 2867.2, + "tls_ms": 264.5, + "ray": "a4b1ac07ef0d46d4-DFW", + "instance": "28b7af4d-c44c-49fd-a1d1-a28e2cea134c", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 2, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 589.9, + "total_ms": 592.0, + "tls_ms": 348.0, + "ray": "a4b1ac1dd9b43ac6-DFW", + "instance": "55cadb14-ed85-4c98-ba09-c2ee66798df9", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 653.3, + "total_ms": 653.8, + "tls_ms": 412.0, + "ray": "a4b1ac256df1d132-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2460.6, + "total_ms": 2461.0, + "tls_ms": 713.7, + "ray": "a4b1ac300f733467-DFW", + "instance": "7a247333-b00c-45e7-83d4-45791f4db0a3", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1983.6, + "total_ms": 1984.0, + "tls_ms": 734.4, + "ray": "a4b1ac426fc1a922-DFW", + "instance": "2a80dc96-e1db-4c95-8e2f-39aeb113f2d0", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "tiny", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1912.6, + "total_ms": 1913.2, + "tls_ms": 1016.9, + "ray": "a4b1ac54bc0b0dc1-DFW", + "instance": "f8fd25a1-7f7c-4df5-bcf9-6b50b1451d07", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 1561.5, + "total_ms": 1562.1, + "tls_ms": 766.5, + "ray": "a4b1ac617d43f085-DFW", + "instance": "6624a15a-bb98-4f51-a04a-51946e16dc20", + "instance_request": "1", + "phase_ms": "19", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 19, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 1684.5, + "total_ms": 1685.1, + "tls_ms": 951.0, + "ray": "a4b1ac707b472ccd-DFW", + "instance": "0916c084-f903-44d5-9e2e-876155768171", + "instance_request": "1", + "phase_ms": "9", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 9, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 1834.8, + "total_ms": 1837.4, + "tls_ms": 920.3, + "ray": "a4b1ac7db83d4403-DFW", + "instance": "d9c8c14c-952f-4a1e-8245-df948b9f20e7", + "instance_request": "1", + "phase_ms": "18", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 19, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 2100.8, + "total_ms": 2101.1, + "tls_ms": 1103.2, + "ray": "a4b1ac8cba783b9b-DFW", + "instance": "dbd26777-8535-4042-87d4-10bf0b52b3bf", + "instance_request": "1", + "phase_ms": "156", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 158, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 1774.2, + "total_ms": 1774.6, + "tls_ms": 873.5, + "ray": "a4b1ac9c8eb8acd3-DFW", + "instance": "64e56fa2-c5d7-4461-89f0-458223828f29", + "instance_request": "1", + "phase_ms": "124", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 127, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 2509.1, + "total_ms": 2509.7, + "tls_ms": 1093.9, + "ray": "a4b1acac78c94654-DFW", + "instance": "823a52c1-b8c9-4df4-b1b1-3239a7698f49", + "instance_request": "1", + "phase_ms": "134", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 137, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + } + ], + "repeat": [ + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 157.1, + "total_ms": 159.4, + "tls_ms": 79.7, + "after_connection_ms": 77.3, + "ray": "a4b1ad517bda46ce-DFW", + "instance": "823a52c1-b8c9-4df4-b1b1-3239a7698f49", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 1003.1, + "total_ms": 1006.1, + "tls_ms": 81.8, + "after_connection_ms": 921.2, + "ray": "a4b1ad55cc5a69bf-DFW", + "instance": "e8b88151-0d30-44e4-bc66-de6b8257e369", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 164.6, + "total_ms": 166.9, + "tls_ms": 86.4, + "after_connection_ms": 78.2, + "ray": "a4b1ad5f5cde9bde-DFW", + "instance": "f8fd25a1-7f7c-4df5-bcf9-6b50b1451d07", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 1385.5, + "total_ms": 1385.9, + "tls_ms": 78.5, + "after_connection_ms": 1307.0, + "ray": "a4b1ad6388ffb52d-DFW", + "instance": "9a4d0d14-19d4-4a68-86f8-5a35063903d4", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 172.4, + "total_ms": 174.8, + "tls_ms": 92.5, + "after_connection_ms": 79.8, + "ray": "a4b1ad6f8b51daec-DFW", + "instance": "d9c8c14c-952f-4a1e-8245-df948b9f20e7", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 167.3, + "total_ms": 169.4, + "tls_ms": 83.2, + "after_connection_ms": 84.0, + "ray": "a4b1ad73ce998f63-DFW", + "instance": "d9c8c14c-952f-4a1e-8245-df948b9f20e7", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 164.4, + "total_ms": 167.1, + "tls_ms": 85.4, + "after_connection_ms": 79.0, + "ray": "a4b1ad781978f2f6-DFW", + "instance": "40a5cd14-6082-4411-b78f-5f06349ad02e", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 1801.8, + "total_ms": 1804.3, + "tls_ms": 81.9, + "after_connection_ms": 1719.9, + "ray": "a4b1ad7c4fbd6c32-DFW", + "instance": "b37b7d50-41e8-4e3a-ac0c-2fe089ad7b17", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 3, + "wall_ms": 6, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 150.8, + "total_ms": 151.0, + "tls_ms": 82.2, + "after_connection_ms": 68.5, + "ray": "a4b1ad8acbb0f2f0-DFW", + "instance": "40a5cd14-6082-4411-b78f-5f06349ad02e", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 684.9, + "total_ms": 685.0, + "tls_ms": 78.5, + "after_connection_ms": 606.3, + "ray": "a4b1ad8efc023239-DFW", + "instance": "f0434608-eab7-4896-ab27-91f574d9c0fd", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 189.5, + "total_ms": 189.8, + "tls_ms": 86.1, + "after_connection_ms": 103.4, + "ray": "a4b1ad96894c6b40-DFW", + "instance": "b37b7d50-41e8-4e3a-ac0c-2fe089ad7b17", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 153.5, + "total_ms": 155.9, + "tls_ms": 81.6, + "after_connection_ms": 71.9, + "ray": "a4b1ad9aed27188a-DFW", + "instance": "f8fd25a1-7f7c-4df5-bcf9-6b50b1451d07", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 145.5, + "total_ms": 145.7, + "tls_ms": 81.1, + "after_connection_ms": 64.4, + "ray": "a4b1ad9f0b603822-DFW", + "instance": "9a4d0d14-19d4-4a68-86f8-5a35063903d4", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 160.4, + "total_ms": 163.5, + "tls_ms": 82.4, + "after_connection_ms": 78.0, + "ray": "a4b1ada33a871427-DFW", + "instance": "f0434608-eab7-4896-ab27-91f574d9c0fd", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 194.5, + "total_ms": 196.9, + "tls_ms": 119.5, + "after_connection_ms": 75.0, + "ray": "a4b1ada7ce5e5aba-DFW", + "instance": "f8fd25a1-7f7c-4df5-bcf9-6b50b1451d07", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 186.8, + "total_ms": 189.3, + "tls_ms": 83.2, + "after_connection_ms": 103.6, + "ray": "a4b1adac0eb9feb2-DFW", + "instance": "f8fd25a1-7f7c-4df5-bcf9-6b50b1451d07", + "instance_request": "5", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 144.0, + "total_ms": 146.3, + "tls_ms": 85.3, + "after_connection_ms": 58.6, + "ray": "a4b1adb07ed1b1c9-DFW", + "instance": "dbd26777-8535-4042-87d4-10bf0b52b3bf", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 158.9, + "total_ms": 159.3, + "tls_ms": 101.0, + "after_connection_ms": 57.8, + "ray": "a4b1adb4c8d0f050-DFW", + "instance": "6624a15a-bb98-4f51-a04a-51946e16dc20", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 142.2, + "total_ms": 144.4, + "tls_ms": 79.2, + "after_connection_ms": 62.9, + "ray": "a4b1adb8d98b61f3-DFW", + "instance": "d9c8c14c-952f-4a1e-8245-df948b9f20e7", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + }, + { + "worker": "tiny", + "run": "repeat", + "path": "/noop", + "status": 200, + "ttfb_ms": 3223.5, + "total_ms": 3224.3, + "tls_ms": 82.2, + "after_connection_ms": 3141.2, + "ray": "a4b1adbcfaaaddab-DFW", + "instance": "f8957529-54d5-4e05-a5c2-24ccbad1d7c1", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "b7cde89f-c6e2-49e9-b2b5-034bd731e6a1" + } + ] + } + }, + { + "worker": "full", + "deployed_name": "tanstack-cold-probe-full", + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a", + "upload_kib": 26263.71, + "gzip_kib": 8459.83, + "reported_startup_ms": 35, + "runs": { + "initial": [ + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2115.7, + "total_ms": 2118.0, + "tls_ms": 370.5, + "after_connection_ms": 1745.1, + "ray": "a4b1aec4fc13f07d-DFW", + "instance": "b51e6b64-daba-4e54-82d3-a3682eb06278", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2324.7, + "total_ms": 2324.9, + "tls_ms": 838.0, + "after_connection_ms": 1486.6, + "ray": "a4b1aedb5b814c1c-DFW", + "instance": "d5ab29e2-1670-46ca-a6ae-238b4287dd4e", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2124.7, + "total_ms": 2128.7, + "tls_ms": 773.4, + "after_connection_ms": 1351.3, + "ray": "a4b1aeebde16f2a1-DFW", + "instance": "c3d79444-3537-4ded-b045-faff1bbd9bad", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2377.4, + "total_ms": 2379.6, + "tls_ms": 80.6, + "after_connection_ms": 2296.7, + "ray": "a4b1aef5ed5f45ef-DFW", + "instance": "ee0f077c-ea19-460b-bec2-6e28d993d288", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 734.8, + "total_ms": 737.2, + "tls_ms": 79.7, + "after_connection_ms": 655.0, + "ray": "a4b1af07f8b32d47-DFW", + "instance": "0483f208-6fd6-40d9-8f1c-fed7ad932bd5", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1107.6, + "total_ms": 1108.0, + "tls_ms": 88.7, + "after_connection_ms": 1018.9, + "ray": "a4b1af0fe9b2488b-DFW", + "instance": "ceb72b82-916e-49a4-9319-3b8343dc0341", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1080.8, + "total_ms": 1083.1, + "tls_ms": 80.0, + "after_connection_ms": 1000.7, + "ray": "a4b1af19fa4ad41c-DFW", + "instance": "ed97f006-eb46-46f2-843f-f754371aaeca", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2334.3, + "total_ms": 2334.5, + "tls_ms": 82.8, + "after_connection_ms": 2251.4, + "ray": "a4b1af2408136be0-DFW", + "instance": "c981cee4-e136-4863-8a87-25ab87c5f1f2", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 168.7, + "total_ms": 171.3, + "tls_ms": 85.2, + "after_connection_ms": 83.4, + "ray": "a4b1af35dbc25cf4-DFW", + "instance": "c3d79444-3537-4ded-b045-faff1bbd9bad", + "instance_request": "2", + "phase_ms": "25", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 24, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 2359.6, + "total_ms": 2362.0, + "tls_ms": 82.4, + "after_connection_ms": 2277.2, + "ray": "a4b1af3a2a273587-DFW", + "instance": "41b888c7-340e-4791-afa3-3abd98e67325", + "instance_request": "1", + "phase_ms": "32", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 33, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 202.9, + "total_ms": 203.2, + "tls_ms": 79.1, + "after_connection_ms": 123.7, + "ray": "a4b1af4c1a5d8617-DFW", + "instance": "c3d79444-3537-4ded-b045-faff1bbd9bad", + "instance_request": "3", + "phase_ms": "6", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 6, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 272.3, + "total_ms": 274.6, + "tls_ms": 80.2, + "after_connection_ms": 192.0, + "ray": "a4b1af50aa7187f4-DFW", + "instance": "0483f208-6fd6-40d9-8f1c-fed7ad932bd5", + "instance_request": "2", + "phase_ms": "123", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 123, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 321.5, + "total_ms": 323.7, + "tls_ms": 80.5, + "after_connection_ms": 241.0, + "ray": "a4b1af55afb947a3-DFW", + "instance": "ee0f077c-ea19-460b-bec2-6e28d993d288", + "instance_request": "2", + "phase_ms": "127", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 130, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 1950.9, + "total_ms": 1953.4, + "tls_ms": 84.6, + "after_connection_ms": 1866.1, + "ray": "a4b1af5aee05e7db-DFW", + "instance": "5d525f91-5667-491a-b7fb-fdb89fc97ee5", + "instance_request": "1", + "phase_ms": "109", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 111, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 522.8, + "total_ms": 654.4, + "tls_ms": 83.5, + "after_connection_ms": 439.2, + "ray": "a4b1af6a6e78a75c-DFW", + "instance": "0483f208-6fd6-40d9-8f1c-fed7ad932bd5", + "instance_request": "3", + "phase_ms": "0", + "server_timing": "app_headers;dur=139.0, db_config;dur=0.0, content_edge_read;dur=268.0, start_handler;dur=139.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 245, + "wall_ms": 1137, + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "8759406b-5f28-4f0a-aa41-024c1800cb0f", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1af6a6e78a75c", + "status": 200, + "headersReadyMs": 139, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 123 + }, + { + "name": "content_edge_read", + "startMs": 123, + "durationMs": 6 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 129 + }, + { + "name": "content_edge_read", + "startMs": 129, + "durationMs": 10 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 139 + } + ] + }, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 2047.0, + "total_ms": 2162.7, + "tls_ms": 84.3, + "after_connection_ms": 1962.6, + "ray": "a4b1af71bf586a08-DFW", + "instance": "913c24a0-5e5f-4c6e-9e23-8d2d110d3db2", + "instance_request": "1", + "phase_ms": "0", + "server_timing": "app_headers;dur=292.0, db_config;dur=0.0, content_edge_read;dur=575.0, start_handler;dur=292.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 306, + "wall_ms": 588, + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "7ad7a15b-2e10-45cb-8fe3-879373eb0cb3", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1af71bf586a08", + "status": 200, + "headersReadyMs": 292, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 283 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 283 + }, + { + "name": "content_edge_read", + "startMs": 283, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 292 + } + ] + }, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 1738.1, + "total_ms": 1856.5, + "tls_ms": 89.0, + "after_connection_ms": 1649.0, + "ray": "a4b1af827f0728b4-DFW", + "instance": "660484cd-6418-4fd1-881d-420d0abc4c3e", + "instance_request": "1", + "phase_ms": "0", + "server_timing": "app_headers;dur=197.0, db_config;dur=0.0, content_edge_read;dur=389.0, start_handler;dur=197.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 346, + "wall_ms": 559, + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "2ee5eedc-3d83-42ce-a10c-1b8cf6583125", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1af827f0728b4", + "status": 200, + "headersReadyMs": 197, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 191 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 192 + }, + { + "name": "content_edge_read", + "startMs": 191, + "durationMs": 6 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 197 + } + ] + }, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 2676.7, + "total_ms": 2811.5, + "tls_ms": 209.3, + "after_connection_ms": 2467.3, + "ray": "a4b1af9218656c82-DFW", + "instance": "a3751a35-b68c-41e9-881d-20fb84d6c1ea", + "instance_request": "1", + "phase_ms": "0", + "server_timing": "app_headers;dur=354.0, db_config;dur=0.0, content_edge_read;dur=694.0, start_handler;dur=354.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 317, + "wall_ms": 677, + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "38ac2249-4edc-40ec-897e-e1d615e8d228", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1af9218656c82", + "status": 200, + "headersReadyMs": 354, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 339 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 340 + }, + { + "name": "content_edge_read", + "startMs": 339, + "durationMs": 15 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 354 + } + ] + }, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 511.7, + "total_ms": 630.0, + "tls_ms": 78.9, + "after_connection_ms": 432.8, + "ray": "a4b1afa61e99f06e-DFW", + "instance": "b51e6b64-daba-4e54-82d3-a3682eb06278", + "instance_request": "2", + "phase_ms": "0", + "server_timing": "app_headers;dur=165.0, db_config;dur=0.0, content_edge_read;dur=323.0, start_handler;dur=165.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 184, + "wall_ms": 349, + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "fd0a77b9-37dd-43b5-98bc-9d275bf007a1", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1afa61e99f06e", + "status": 200, + "headersReadyMs": 165, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 157 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 158 + }, + { + "name": "content_edge_read", + "startMs": 157, + "durationMs": 8 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 165 + } + ] + }, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 202.7, + "total_ms": 205.1, + "tls_ms": 84.2, + "after_connection_ms": 118.5, + "ray": "a4b1afad5b7a6bcc-DFW", + "instance": "a3751a35-b68c-41e9-881d-20fb84d6c1ea", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + }, + { + "worker": "full", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 167.1, + "total_ms": 167.3, + "tls_ms": 86.2, + "after_connection_ms": 80.9, + "ray": "a4b1afb1d82a66dd-DFW", + "instance": "913c24a0-5e5f-4c6e-9e23-8d2d110d3db2", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "54beb47a-1efb-4147-bb66-5c2ae0a3a79a" + } + ] + } + }, + { + "worker": "data", + "deployed_name": "tanstack-cold-probe-data", + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0", + "upload_kib": 22615.06, + "gzip_kib": 6934.42, + "reported_startup_ms": 1, + "runs": { + "initial": [ + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 389.0, + "total_ms": 389.5, + "tls_ms": 167.6, + "after_connection_ms": 221.3, + "ray": "a4b1b0262ff1ddb0-DFW", + "instance": "25c8cafb-8a2b-4916-8b2a-1cdcaf889f5b", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 592.9, + "total_ms": 593.2, + "tls_ms": 214.2, + "after_connection_ms": 378.6, + "ray": "a4b1b02c89b34630-DFW", + "instance": "f9784f66-0d48-4a56-9f51-6fccfa573c39", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 471.9, + "total_ms": 472.1, + "tls_ms": 221.9, + "after_connection_ms": 249.9, + "ray": "a4b1b03389c7a9f5-DFW", + "instance": "c6f8e8f5-2925-4480-babd-3707039093d0", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 549.3, + "total_ms": 549.5, + "tls_ms": 229.5, + "after_connection_ms": 319.7, + "ray": "a4b1b039cb9ab476-DFW", + "instance": "c77d984f-cfee-4063-ae91-f820a0953f30", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 3, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 551.6, + "total_ms": 554.3, + "tls_ms": 236.9, + "after_connection_ms": 314.7, + "ray": "a4b1b0408aeae718-DFW", + "instance": "cd8ff136-62a5-4ae4-9302-912ed0f8907a", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 216.4, + "total_ms": 218.7, + "tls_ms": 167.1, + "after_connection_ms": 49.2, + "ray": "a4b1b0464a1e5494-DFW", + "instance": "c77d984f-cfee-4063-ae91-f820a0953f30", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 264.2, + "total_ms": 264.4, + "tls_ms": 79.0, + "after_connection_ms": 185.1, + "ray": "a4b1b04a5eabf887-DFW", + "instance": "41af4d94-e749-45bc-b7d0-c44330035ea3", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 144.4, + "total_ms": 146.8, + "tls_ms": 79.7, + "after_connection_ms": 64.7, + "ray": "a4b1b04f3e76ebf6-DFW", + "instance": "cd8ff136-62a5-4ae4-9302-912ed0f8907a", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 211.8, + "total_ms": 214.1, + "tls_ms": 105.9, + "after_connection_ms": 105.8, + "ray": "a4b1b0538ada46cc-DFW", + "instance": "f9784f66-0d48-4a56-9f51-6fccfa573c39", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 347.9, + "total_ms": 350.2, + "tls_ms": 80.4, + "after_connection_ms": 267.5, + "ray": "a4b1b057eead670d-DFW", + "instance": "b4f06967-0af7-41e6-9492-9c32307923fc", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 174.1, + "total_ms": 176.8, + "tls_ms": 78.6, + "after_connection_ms": 95.5, + "ray": "a4b1b05d4f61cb67-DFW", + "instance": "c77d984f-cfee-4063-ae91-f820a0953f30", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 557.4, + "total_ms": 557.7, + "tls_ms": 82.8, + "after_connection_ms": 474.5, + "ray": "a4b1b061bbfc4854-DFW", + "instance": "986b3d5c-403c-42e5-aeff-bae8fb878a7f", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 228.7, + "total_ms": 229.0, + "tls_ms": 78.5, + "after_connection_ms": 150.2, + "ray": "a4b1b0685aa9460b-DFW", + "instance": "f9784f66-0d48-4a56-9f51-6fccfa573c39", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 507.2, + "total_ms": 507.6, + "tls_ms": 86.9, + "after_connection_ms": 420.3, + "ray": "a4b1b06d1a381002-DFW", + "instance": "d6aab238-7341-446c-918e-759314e69cd8", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 145.6, + "total_ms": 148.0, + "tls_ms": 79.8, + "after_connection_ms": 65.7, + "ray": "a4b1b07368d76a82-DFW", + "instance": "b3fa874b-5d78-417d-988b-1302ed8e1fa2", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 135.0, + "total_ms": 135.2, + "tls_ms": 79.9, + "after_connection_ms": 55.0, + "ray": "a4b1b077891069d3-DFW", + "instance": "b3fa874b-5d78-417d-988b-1302ed8e1fa2", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 553.6, + "total_ms": 556.0, + "tls_ms": 83.2, + "after_connection_ms": 470.3, + "ray": "a4b1b07ba8386c33-DFW", + "instance": "8a5a0daa-0720-435f-9937-f6bbcee5374a", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 407.8, + "total_ms": 410.1, + "tls_ms": 90.2, + "after_connection_ms": 317.5, + "ray": "a4b1b0826a99b16f-DFW", + "instance": "0c1dbb8a-bd41-4d36-b000-7e39cf10a1c3", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 169.3, + "total_ms": 171.5, + "tls_ms": 83.6, + "after_connection_ms": 85.7, + "ray": "a4b1b0881efe295c-DFW", + "instance": "c77d984f-cfee-4063-ae91-f820a0953f30", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + }, + { + "worker": "data", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 220.4, + "total_ms": 222.9, + "tls_ms": 139.4, + "after_connection_ms": 81.0, + "ray": "a4b1b08cbe5b1021-DFW", + "instance": "26fc7ac0-d10b-4908-b56b-efb180b93375", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "7abef817-42f3-4947-b156-bf4a38e62cb0" + } + ] + } + }, + { + "worker": "no-wasm", + "deployed_name": "tanstack-cold-probe-no-wasm", + "version": "064f1ab1-74a8-4525-83de-495090637549", + "upload_kib": 22617.0, + "gzip_kib": 6933.89, + "reported_startup_ms": 2, + "runs": { + "initial": [ + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2033.7, + "total_ms": 2036.1, + "tls_ms": 212.2, + "after_connection_ms": 1821.5, + "ray": "a4b1b0b96eee6ba9-DFW", + "instance": "3856de84-fb4c-4d0a-af7d-495ccf831619", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 847.8, + "total_ms": 850.4, + "tls_ms": 81.0, + "after_connection_ms": 766.7, + "ray": "a4b1b0c878fed8db-DFW", + "instance": "f27aa11c-dc50-4bcc-8ca0-97da53e9f2ee", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1011.6, + "total_ms": 1011.9, + "tls_ms": 82.4, + "after_connection_ms": 929.2, + "ray": "a4b1b0d1089e7d9f-DFW", + "instance": "3b1cc40c-137b-4a81-a435-c23df0cade24", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1547.3, + "total_ms": 1547.6, + "tls_ms": 149.6, + "after_connection_ms": 1397.6, + "ray": "a4b1b0dbcc218835-DFW", + "instance": "24589a27-ef31-4f17-9c5e-59b3b0b1cd31", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1037.7, + "total_ms": 1040.2, + "tls_ms": 176.3, + "after_connection_ms": 861.4, + "ray": "a4b1b0e809a3f68c-DFW", + "instance": "f467a255-3342-4266-a562-0676ed133bd6", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 955.8, + "total_ms": 958.1, + "tls_ms": 82.2, + "after_connection_ms": 873.5, + "ray": "a4b1b0f12b69e68b-DFW", + "instance": "f934780e-e25a-49ca-8235-3e749a7aa713", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 133.9, + "total_ms": 136.2, + "tls_ms": 82.3, + "after_connection_ms": 51.5, + "ray": "a4b1b0fa59ab377b-DFW", + "instance": "24589a27-ef31-4f17-9c5e-59b3b0b1cd31", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 845.6, + "total_ms": 846.1, + "tls_ms": 79.0, + "after_connection_ms": 766.6, + "ray": "a4b1b0fe6f4c6f16-DFW", + "instance": "c068998c-0ffd-4bed-b428-9265b9c08944", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1698.1, + "total_ms": 1698.5, + "tls_ms": 81.7, + "after_connection_ms": 1616.4, + "ray": "a4b1b106efbb3160-DFW", + "instance": "3a14a23a-79a7-4045-8b1c-b5e78f43dbcc", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 3, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 159.4, + "total_ms": 161.7, + "tls_ms": 81.2, + "after_connection_ms": 78.2, + "ray": "a4b1b114bb9aaad5-DFW", + "instance": "24589a27-ef31-4f17-9c5e-59b3b0b1cd31", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 147.2, + "total_ms": 149.6, + "tls_ms": 79.6, + "after_connection_ms": 67.5, + "ray": "a4b1b118fb7edc67-DFW", + "instance": "3a14a23a-79a7-4045-8b1c-b5e78f43dbcc", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 2, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 208.0, + "total_ms": 208.4, + "tls_ms": 116.6, + "after_connection_ms": 91.4, + "ray": "a4b1b11d8e85b94c-DFW", + "instance": "24589a27-ef31-4f17-9c5e-59b3b0b1cd31", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 131.6, + "total_ms": 131.9, + "tls_ms": 80.8, + "after_connection_ms": 50.7, + "ray": "a4b1b121cc58f0b6-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 167.9, + "total_ms": 170.2, + "tls_ms": 80.2, + "after_connection_ms": 87.6, + "ray": "a4b1b125cc5b2d41-DFW", + "instance": "3a14a23a-79a7-4045-8b1c-b5e78f43dbcc", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 149.5, + "total_ms": 149.7, + "tls_ms": 82.6, + "after_connection_ms": 66.9, + "ray": "a4b1b12a1e492628-DFW", + "instance": "f467a255-3342-4266-a562-0676ed133bd6", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 144.0, + "total_ms": 146.2, + "tls_ms": 77.1, + "after_connection_ms": 66.9, + "ray": "a4b1b12e3d1c3479-DFW", + "instance": "3a14a23a-79a7-4045-8b1c-b5e78f43dbcc", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 2, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 170.9, + "total_ms": 171.2, + "tls_ms": 81.8, + "after_connection_ms": 89.0, + "ray": "a4b1b1326aedb476-DFW", + "instance": "24589a27-ef31-4f17-9c5e-59b3b0b1cd31", + "instance_request": "5", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 130.5, + "total_ms": 132.7, + "tls_ms": 78.7, + "after_connection_ms": 51.8, + "ray": "a4b1b136ae245494-DFW", + "instance": "24589a27-ef31-4f17-9c5e-59b3b0b1cd31", + "instance_request": "6", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 914.1, + "total_ms": 917.0, + "tls_ms": 78.1, + "after_connection_ms": 836.0, + "ray": "a4b1b13ab8615298-DFW", + "instance": "a20827d8-83e2-47ed-936c-792b663bd1e2", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 139.4, + "total_ms": 140.0, + "tls_ms": 81.9, + "after_connection_ms": 57.4, + "ray": "a4b1b143b879a8b3-DFW", + "instance": "f467a255-3342-4266-a562-0676ed133bd6", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 500, + "ttfb_ms": 282.8, + "total_ms": 286.3, + "tls_ms": 81.8, + "after_connection_ms": 200.9, + "ray": "a4b1b147d9f6f063-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 174.2, + "total_ms": 174.8, + "tls_ms": 86.3, + "after_connection_ms": 87.8, + "ray": "a4b1b14ce8814d98-DFW", + "instance": "f934780e-e25a-49ca-8235-3e749a7aa713", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 137.3, + "total_ms": 141.9, + "tls_ms": 84.1, + "after_connection_ms": 53.1, + "ray": "a4b1b151582938f4-DFW", + "instance": "f467a255-3342-4266-a562-0676ed133bd6", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 157.6, + "total_ms": 158.0, + "tls_ms": 83.7, + "after_connection_ms": 73.9, + "ray": "a4b1b1557aabb52a-DFW", + "instance": "f467a255-3342-4266-a562-0676ed133bd6", + "instance_request": "5", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + } + ], + "settled": [ + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 701.6, + "total_ms": 704.1, + "tls_ms": 128.6, + "after_connection_ms": 572.9, + "ray": "a4b1bd392f231234-DFW", + "instance": "0f87a187-a175-4ebc-a9cc-b1e5514bc1c9", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 1410.4, + "total_ms": 1412.8, + "tls_ms": 84.7, + "after_connection_ms": 1325.6, + "ray": "a4b1bd409fe92caa-DFW", + "instance": "ca212110-d2b0-4ecd-bada-ce7f5e2c070b", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 154.1, + "total_ms": 156.5, + "tls_ms": 95.5, + "after_connection_ms": 58.5, + "ray": "a4b1bd4cc98e7c9d-DFW", + "instance": "0f87a187-a175-4ebc-a9cc-b1e5514bc1c9", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 159.4, + "total_ms": 161.7, + "tls_ms": 80.8, + "after_connection_ms": 78.5, + "ray": "a4b1bd50daba6e6f-DFW", + "instance": "0f87a187-a175-4ebc-a9cc-b1e5514bc1c9", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 2254.2, + "total_ms": 2256.7, + "tls_ms": 83.9, + "after_connection_ms": 2170.3, + "ray": "a4b1bd551d7afc87-DFW", + "instance": "51162afe-54b1-4b6d-ba78-ea84c5ca481d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 1002.6, + "total_ms": 1005.2, + "tls_ms": 82.8, + "after_connection_ms": 919.7, + "ray": "a4b1bd667f1ff030-DFW", + "instance": "7cb70131-3a0b-42c1-86e2-036511875327", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 143.1, + "total_ms": 145.5, + "tls_ms": 80.1, + "after_connection_ms": 62.9, + "ray": "a4b1bd6fff41b901-DFW", + "instance": "51162afe-54b1-4b6d-ba78-ea84c5ca481d", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 871.3, + "total_ms": 873.7, + "tls_ms": 78.5, + "after_connection_ms": 792.7, + "ray": "a4b1bd742d4398b0-DFW", + "instance": "7126bf4c-947d-40c9-a9e9-4a6948987d5d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 948.2, + "total_ms": 948.6, + "tls_ms": 83.5, + "after_connection_ms": 864.6, + "ray": "a4b1bd7ceb3fc641-DFW", + "instance": "cc1cf90e-7d54-4e8b-a3f3-0f036f5a6666", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 2019.6, + "total_ms": 2022.8, + "tls_ms": 85.4, + "after_connection_ms": 1934.1, + "ray": "a4b1bd861a0a6bfb-DFW", + "instance": "81f33339-fede-4c8d-8d43-92c7f2e1d601", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 138.7, + "total_ms": 141.0, + "tls_ms": 78.2, + "after_connection_ms": 60.4, + "ray": "a4b1bd95fb26d990-DFW", + "instance": "7126bf4c-947d-40c9-a9e9-4a6948987d5d", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 162.1, + "total_ms": 164.5, + "tls_ms": 78.2, + "after_connection_ms": 83.9, + "ray": "a4b1bd9a0baaf78b-DFW", + "instance": "51162afe-54b1-4b6d-ba78-ea84c5ca481d", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 227.9, + "total_ms": 230.2, + "tls_ms": 109.1, + "after_connection_ms": 118.7, + "ray": "a4b1bd9e9c1d1427-DFW", + "instance": "51162afe-54b1-4b6d-ba78-ea84c5ca481d", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 155.5, + "total_ms": 157.9, + "tls_ms": 80.2, + "after_connection_ms": 75.3, + "ray": "a4b1bda2fe10857d-DFW", + "instance": "cc1cf90e-7d54-4e8b-a3f3-0f036f5a6666", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 182.9, + "total_ms": 185.3, + "tls_ms": 78.1, + "after_connection_ms": 104.8, + "ray": "a4b1bda72e557d9f-DFW", + "instance": "0f87a187-a175-4ebc-a9cc-b1e5514bc1c9", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "064f1ab1-74a8-4525-83de-495090637549" + }, + { + "worker": "no-wasm", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 154.5, + "total_ms": 156.8, + "tls_ms": 85.0, + "after_connection_ms": 69.5, + "ray": "a4b1bdab9d9ef787-DFW", + "instance": "51162afe-54b1-4b6d-ba78-ea84c5ca481d", + "instance_request": "5", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "064f1ab1-74a8-4525-83de-495090637549" + } + ] + } + }, + { + "worker": "single-js", + "deployed_name": "tanstack-cold-probe-single-js", + "version": "b25cb085-0e77-4053-be83-70ce147db3cc", + "upload_kib": 23239.25, + "gzip_kib": 6970.87, + "reported_startup_ms": 3, + "runs": { + "initial": [ + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 1272.6, + "total_ms": 1273.0, + "tls_ms": 762.3, + "after_connection_ms": 510.3, + "ray": "a4b1b34c5d60c4f5-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 1253.3, + "total_ms": 1253.6, + "tls_ms": 816.5, + "after_connection_ms": 436.8, + "ray": "a4b1b3572e483ac1-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 199.2, + "total_ms": 199.5, + "tls_ms": 121.6, + "after_connection_ms": 77.5, + "ray": "a4b1b35bcb226b3d-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 579.9, + "total_ms": 580.1, + "tls_ms": 86.1, + "after_connection_ms": 493.7, + "ray": "a4b1b36008310ce7-DFW", + "instance": "9ed67df5-9b75-482c-b39e-ab549a8ccfa0", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 558.9, + "total_ms": 559.2, + "tls_ms": 97.0, + "after_connection_ms": 461.8, + "ray": "a4b1b366eee2eff2-DFW", + "instance": "4938d0fe-9b3f-42b6-a516-11b2e5c91d6b", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 128.7, + "total_ms": 128.9, + "tls_ms": 81.3, + "after_connection_ms": 47.3, + "ray": "a4b1b36d8867feb2-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 170.6, + "total_ms": 170.8, + "tls_ms": 102.0, + "after_connection_ms": 68.5, + "ray": "a4b1b371ae34aad5-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 127.4, + "total_ms": 129.7, + "tls_ms": 88.0, + "after_connection_ms": 39.4, + "ray": "a4b1b375df454403-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 500, + "ttfb_ms": 314.2, + "total_ms": 316.2, + "tls_ms": 90.4, + "after_connection_ms": 223.7, + "ray": "a4b1b379fbab6bcc-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 144.0, + "total_ms": 146.8, + "tls_ms": 83.1, + "after_connection_ms": 60.8, + "ray": "a4b1b37f2c92ebf6-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 404, + "ttfb_ms": 131.8, + "total_ms": 134.2, + "tls_ms": 78.0, + "after_connection_ms": 53.7, + "ray": "a4b1b38349ae52e4-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 573.1, + "total_ms": 575.4, + "tls_ms": 96.1, + "after_connection_ms": 476.8, + "ray": "a4b1b3877f5f0864-DFW", + "instance": "276cc60f-d808-4657-9063-6a75fae27857", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 501.5, + "total_ms": 503.8, + "tls_ms": 78.8, + "after_connection_ms": 422.6, + "ray": "a4b1b38e29b56dd6-DFW", + "instance": "f1d62490-ce29-460d-9e55-d0475b66ff90", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 608.5, + "total_ms": 608.9, + "tls_ms": 80.3, + "after_connection_ms": 528.1, + "ray": "a4b1b3949ba0c6dd-DFW", + "instance": "48d8bcd1-630d-490f-8ce3-cf1989ad48c0", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 500, + "ttfb_ms": 288.0, + "total_ms": 290.4, + "tls_ms": 87.1, + "after_connection_ms": 200.8, + "ray": "a4b1b39ba80d3875-DFW", + "instance": null, + "instance_request": null, + "phase_ms": null, + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 744.1, + "total_ms": 746.5, + "tls_ms": 84.0, + "after_connection_ms": 660.1, + "ray": "a4b1b3a0b85e37ee-DFW", + "instance": "5d1415d7-7440-4b13-a644-5d924dbc03de", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 572.3, + "total_ms": 574.6, + "tls_ms": 77.7, + "after_connection_ms": 494.5, + "ray": "a4b1b3a89e045e24-DFW", + "instance": "459bfc0d-bbe6-442d-bfad-571043f29a48", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1351.7, + "total_ms": 1354.0, + "tls_ms": 90.0, + "after_connection_ms": 1261.6, + "ray": "a4b1b3af88216be6-DFW", + "instance": "683f1c76-3f17-4c45-9709-ca46377bf41d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 132.4, + "total_ms": 134.8, + "tls_ms": 86.0, + "after_connection_ms": 46.2, + "ray": "a4b1b3bb38098c58-DFW", + "instance": "046914df-9532-4923-a254-a17fe35a1b97", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 601.1, + "total_ms": 603.5, + "tls_ms": 79.9, + "after_connection_ms": 521.1, + "ray": "a4b1b3bf3c7c69d8-DFW", + "instance": "5794abb7-f144-4de9-b171-92e6f7bd2734", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 634.0, + "total_ms": 636.4, + "tls_ms": 84.3, + "after_connection_ms": 549.6, + "ray": "a4b1b3c659afb66c-DFW", + "instance": "402c81cd-95ae-4192-8705-2d497c44c873", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 630.0, + "total_ms": 630.7, + "tls_ms": 88.3, + "after_connection_ms": 541.7, + "ray": "a4b1b3cd9edff0a0-DFW", + "instance": "c77a9901-96ed-49fc-ab46-d4eeaa6093b5", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 147.8, + "total_ms": 150.1, + "tls_ms": 79.7, + "after_connection_ms": 68.0, + "ray": "a4b1b3d4b937f082-DFW", + "instance": "c77a9901-96ed-49fc-ab46-d4eeaa6093b5", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "single-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 654.7, + "total_ms": 655.4, + "tls_ms": 83.8, + "after_connection_ms": 570.9, + "ray": "a4b1b3d8e8ea3da4-DFW", + "instance": "8403290a-8426-4c3d-a5d5-19db1f5e8ced", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + } + ], + "settled": [ + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 582.5, + "total_ms": 585.0, + "tls_ms": 128.9, + "after_connection_ms": 453.5, + "ray": "a4b1bd27de0f6528-DFW", + "instance": "2be62297-0369-4634-98de-999bd64ad5f6", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 690.6, + "total_ms": 692.9, + "tls_ms": 82.3, + "after_connection_ms": 608.2, + "ray": "a4b1bd2e6ee7f052-DFW", + "instance": "c57ce07c-b465-4964-9c53-39a7e972bf20", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 2, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 2049.3, + "total_ms": 2049.8, + "tls_ms": 84.7, + "after_connection_ms": 1964.6, + "ray": "a4b1bd360e9f469c-DFW", + "instance": "a7b69aeb-b219-41bc-a1b5-735bc4dfefac", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 604.8, + "total_ms": 607.1, + "tls_ms": 86.8, + "after_connection_ms": 517.9, + "ray": "a4b1bd4628b1fa6c-DFW", + "instance": "806f8b9c-01d2-4e09-9696-12cb1ba9a48d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 697.6, + "total_ms": 700.1, + "tls_ms": 93.4, + "after_connection_ms": 604.1, + "ray": "a4b1bd4d3a2ce55e-DFW", + "instance": "0e62ed3b-0cc6-4ceb-b0e1-821e9b21da0f", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 710.2, + "total_ms": 712.6, + "tls_ms": 85.1, + "after_connection_ms": 625.0, + "ray": "a4b1bd54df1c67ae-DFW", + "instance": "1ccb4d10-1c6a-4c57-8bb5-568d99019656", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 188.1, + "total_ms": 190.5, + "tls_ms": 100.3, + "after_connection_ms": 87.7, + "ray": "a4b1bd5c9b9e5aba-DFW", + "instance": "806f8b9c-01d2-4e09-9696-12cb1ba9a48d", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 157.7, + "total_ms": 158.1, + "tls_ms": 79.9, + "after_connection_ms": 77.7, + "ray": "a4b1bd60edbcf78b-DFW", + "instance": "0e62ed3b-0cc6-4ceb-b0e1-821e9b21da0f", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 189.2, + "total_ms": 191.7, + "tls_ms": 88.0, + "after_connection_ms": 101.1, + "ray": "a4b1bd652be24600-DFW", + "instance": "a7b69aeb-b219-41bc-a1b5-735bc4dfefac", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 457.3, + "total_ms": 459.6, + "tls_ms": 363.1, + "after_connection_ms": 94.1, + "ray": "a4b1bd6b6b1d69c6-DFW", + "instance": "1ccb4d10-1c6a-4c57-8bb5-568d99019656", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 130.6, + "total_ms": 133.0, + "tls_ms": 82.0, + "after_connection_ms": 48.5, + "ray": "a4b1bd6fca55f06e-DFW", + "instance": "c57ce07c-b465-4964-9c53-39a7e972bf20", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 607.3, + "total_ms": 609.5, + "tls_ms": 81.1, + "after_connection_ms": 526.1, + "ray": "a4b1bd73eb9c8b23-DFW", + "instance": "1ae0abee-b15b-4116-8226-19a109d7f0a1", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 158.3, + "total_ms": 160.7, + "tls_ms": 83.4, + "after_connection_ms": 74.9, + "ray": "a4b1bd7aeaa23064-DFW", + "instance": "0e62ed3b-0cc6-4ceb-b0e1-821e9b21da0f", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 130.1, + "total_ms": 130.5, + "tls_ms": 78.5, + "after_connection_ms": 51.6, + "ray": "a4b1bd7f28fdc0e5-DFW", + "instance": "2be62297-0369-4634-98de-999bd64ad5f6", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 137.3, + "total_ms": 139.8, + "tls_ms": 82.2, + "after_connection_ms": 55.0, + "ray": "a4b1bd833dc369de-DFW", + "instance": "1ccb4d10-1c6a-4c57-8bb5-568d99019656", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + }, + { + "worker": "single-js", + "run": "settled", + "path": "/noop", + "status": 200, + "ttfb_ms": 170.4, + "total_ms": 170.9, + "tls_ms": 81.7, + "after_connection_ms": 88.6, + "ray": "a4b1bd8759cb37ee-DFW", + "instance": "806f8b9c-01d2-4e09-9696-12cb1ba9a48d", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "b25cb085-0e77-4053-be83-70ce147db3cc" + } + ] + } + }, + { + "worker": "many-js", + "deployed_name": "tanstack-cold-probe-many-js", + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267", + "upload_kib": 23256.81, + "gzip_kib": 7085.31, + "reported_startup_ms": 2, + "runs": { + "initial": [ + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 597.1, + "total_ms": 600.3, + "tls_ms": 131.5, + "after_connection_ms": 465.5, + "ray": "a4b1b4f44d7cceb7-DFW", + "instance": "4c0eed89-05a5-4a11-80ca-6478b6f9f64d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 158.6, + "total_ms": 160.9, + "tls_ms": 88.3, + "after_connection_ms": 70.2, + "ray": "a4b1b4fafb1b51ac-DFW", + "instance": "4c0eed89-05a5-4a11-80ca-6478b6f9f64d", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 588.6, + "total_ms": 589.1, + "tls_ms": 163.6, + "after_connection_ms": 425.0, + "ray": "a4b1b4ffabe624e9-DFW", + "instance": "8a62c4b0-4136-410a-8b1a-fbd2101c9356", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 491.6, + "total_ms": 494.1, + "tls_ms": 84.4, + "after_connection_ms": 407.1, + "ray": "a4b1b5061d04cd65-DFW", + "instance": "f944d1a1-5b17-4bfc-a29f-0a0708823c4f", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 524.2, + "total_ms": 526.5, + "tls_ms": 80.8, + "after_connection_ms": 443.4, + "ray": "a4b1b50c5b051234-DFW", + "instance": "75975720-4d65-42a7-a414-abf11d8dabbc", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 563.3, + "total_ms": 563.6, + "tls_ms": 85.0, + "after_connection_ms": 478.3, + "ray": "a4b1b512dc2e133e-DFW", + "instance": "dc47fe8d-001f-4e0f-8b69-fe4b6a1dd6aa", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 154.7, + "total_ms": 155.2, + "tls_ms": 78.4, + "after_connection_ms": 76.2, + "ray": "a4b1b5199da6504f-DFW", + "instance": "4c0eed89-05a5-4a11-80ca-6478b6f9f64d", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 157.1, + "total_ms": 159.5, + "tls_ms": 83.6, + "after_connection_ms": 73.4, + "ray": "a4b1b51dd9c9e86c-DFW", + "instance": "f944d1a1-5b17-4bfc-a29f-0a0708823c4f", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 533.7, + "total_ms": 536.0, + "tls_ms": 80.5, + "after_connection_ms": 453.2, + "ray": "a4b1b5220b58a9f5-DFW", + "instance": "91858874-ba7a-4eb3-b7e8-929ac2932a22", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 159.3, + "total_ms": 159.6, + "tls_ms": 101.6, + "after_connection_ms": 57.6, + "ray": "a4b1b528e9e3cd65-DFW", + "instance": "f944d1a1-5b17-4bfc-a29f-0a0708823c4f", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 617.4, + "total_ms": 617.7, + "tls_ms": 145.5, + "after_connection_ms": 471.8, + "ray": "a4b1b52d5cdaf098-DFW", + "instance": "6fc22ec6-a3fc-4ecf-8e2d-0a1efc4a8949", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 160.7, + "total_ms": 163.1, + "tls_ms": 82.9, + "after_connection_ms": 77.7, + "ray": "a4b1b5340f499b88-DFW", + "instance": "75975720-4d65-42a7-a414-abf11d8dabbc", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 367.2, + "total_ms": 367.5, + "tls_ms": 80.9, + "after_connection_ms": 286.3, + "ray": "a4b1b53849dd8de5-DFW", + "instance": "9c27da3f-2e4a-47fc-9185-02981acc06e9", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 1, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 524.1, + "total_ms": 526.3, + "tls_ms": 85.6, + "after_connection_ms": 438.4, + "ray": "a4b1b53dc88df02e-DFW", + "instance": "9a8ab7bb-6c3b-4e62-819d-123974fc2eff", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 515.0, + "total_ms": 517.2, + "tls_ms": 81.1, + "after_connection_ms": 433.8, + "ray": "a4b1b544488d1002-DFW", + "instance": "41557c45-7979-4076-87b1-08f4b1004989", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 442.7, + "total_ms": 445.1, + "tls_ms": 78.3, + "after_connection_ms": 364.4, + "ray": "a4b1b54aaa4d3ad5-DFW", + "instance": "db3d348a-8a99-4730-937f-466d635da7a7", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 741.9, + "total_ms": 742.2, + "tls_ms": 81.4, + "after_connection_ms": 660.4, + "ray": "a4b1b550ad06143c-DFW", + "instance": "9cd96f1c-cdf9-4817-832a-0a9c25f24ede", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 165.5, + "total_ms": 167.9, + "tls_ms": 84.0, + "after_connection_ms": 81.4, + "ray": "a4b1b5588976fa34-DFW", + "instance": "4c0eed89-05a5-4a11-80ca-6478b6f9f64d", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 152.6, + "total_ms": 152.8, + "tls_ms": 83.7, + "after_connection_ms": 68.8, + "ray": "a4b1b55ccd9bf0b5-DFW", + "instance": "6fc22ec6-a3fc-4ecf-8e2d-0a1efc4a8949", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 162.8, + "total_ms": 163.1, + "tls_ms": 80.2, + "after_connection_ms": 82.5, + "ray": "a4b1b560f8f42be4-DFW", + "instance": "75975720-4d65-42a7-a414-abf11d8dabbc", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 159.3, + "total_ms": 159.6, + "tls_ms": 80.7, + "after_connection_ms": 78.5, + "ray": "a4b1b5653ba92c52-DFW", + "instance": "75975720-4d65-42a7-a414-abf11d8dabbc", + "instance_request": "4", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 563.3, + "total_ms": 565.7, + "tls_ms": 79.1, + "after_connection_ms": 484.2, + "ray": "a4b1b5696b46d132-DFW", + "instance": "14fd4001-bb6a-420f-9237-b262a1c0568d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 159.5, + "total_ms": 161.9, + "tls_ms": 82.8, + "after_connection_ms": 76.6, + "ray": "a4b1b5702c0df0b5-DFW", + "instance": "6fc22ec6-a3fc-4ecf-8e2d-0a1efc4a8949", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "a0943789-5e3e-4c5c-88dc-fa2c990fb267" + }, + { + "worker": "many-js", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 502.5, + "total_ms": 502.8, + "tls_ms": 88.4, + "after_connection_ms": 414.0, + "ray": "a4b1b5746865f075-DFW", + "instance": "05da536c-3714-470d-ae14-483322437ca0", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + } + ] + } + }, + { + "worker": "monolith", + "deployed_name": "tanstack-cold-probe-monolith", + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b", + "upload_kib": 26244.8, + "gzip_kib": 8413.79, + "reported_startup_ms": 87, + "runs": { + "initial": [ + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1053.1, + "total_ms": 1055.5, + "tls_ms": 141.6, + "after_connection_ms": 911.5, + "ray": "a4b1b871fc74a86e-DFW", + "instance": "243d8377-2cea-4d41-92bc-16acbad1785d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 958.0, + "total_ms": 960.3, + "tls_ms": 84.8, + "after_connection_ms": 873.2, + "ray": "a4b1b87b793eae6e-DFW", + "instance": "a6a9e844-914e-4fac-aa4c-de2e4ce1acb7", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 2016.5, + "total_ms": 2018.6, + "tls_ms": 77.4, + "after_connection_ms": 1939.0, + "ray": "a4b1b8849b6869d9-DFW", + "instance": "5158eb0a-4236-4509-8ff2-4a2e1bbd054d", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1339.1, + "total_ms": 1341.3, + "tls_ms": 80.5, + "after_connection_ms": 1258.5, + "ray": "a4b1b8948f895549-DFW", + "instance": "a17aaf4f-d508-49d8-807f-7a07a5a580c1", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 963.4, + "total_ms": 965.8, + "tls_ms": 79.8, + "after_connection_ms": 883.5, + "ray": "a4b1b8a01afe5843-DFW", + "instance": "e95d8ca5-e614-472b-b454-a5252a0b65ca", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 142.7, + "total_ms": 142.9, + "tls_ms": 76.6, + "after_connection_ms": 66.0, + "ray": "a4b1b8a95f3d686c-DFW", + "instance": "e95d8ca5-e614-472b-b454-a5252a0b65ca", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 226.0, + "total_ms": 228.4, + "tls_ms": 108.1, + "after_connection_ms": 117.9, + "ray": "a4b1b8adaa17bd3d-DFW", + "instance": "a6a9e844-914e-4fac-aa4c-de2e4ce1acb7", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 1872.1, + "total_ms": 1875.0, + "tls_ms": 513.7, + "after_connection_ms": 1358.4, + "ray": "a4b1b8b4eb4c6b30-DFW", + "instance": "612c9735-bfc4-4a9f-8191-1f21fe820843", + "instance_request": "1", + "phase_ms": "0", + "server_timing": null + }, + { + "worker": "monolith", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 1065.3, + "total_ms": 1067.7, + "tls_ms": 316.7, + "after_connection_ms": 748.5, + "ray": "a4b1b8c28d4a5549-DFW", + "instance": "7d65ab5c-b397-4a19-82bd-729992d5eeea", + "instance_request": "1", + "phase_ms": "8", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 8, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 1293.3, + "total_ms": 1296.0, + "tls_ms": 79.0, + "after_connection_ms": 1214.2, + "ray": "a4b1b8caff58005c-DFW", + "instance": "45cdd671-fbba-4a3f-86b9-190c99180b7f", + "instance_request": "1", + "phase_ms": "9", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 9, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/cache", + "status": 200, + "ttfb_ms": 1146.5, + "total_ms": 1147.0, + "tls_ms": 83.3, + "after_connection_ms": 1063.2, + "ray": "a4b1b8d65a12a66d-DFW", + "instance": "8b1e0943-6a26-4fa6-b325-ca5dc9263ba8", + "instance_request": "1", + "phase_ms": "10", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 11, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 596.4, + "total_ms": 596.7, + "tls_ms": 80.7, + "after_connection_ms": 515.7, + "ray": "a4b1b8e0b8656a3f-DFW", + "instance": "5158eb0a-4236-4509-8ff2-4a2e1bbd054d", + "instance_request": "2", + "phase_ms": "352", + "server_timing": null, + "cpu_ms": 2, + "wall_ms": 356, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 381.1, + "total_ms": 381.4, + "tls_ms": 82.5, + "after_connection_ms": 298.5, + "ray": "a4b1b8e7bed71d52-DFW", + "instance": "243d8377-2cea-4d41-92bc-16acbad1785d", + "instance_request": "2", + "phase_ms": "238", + "server_timing": null, + "cpu_ms": 1, + "wall_ms": 239, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/r2", + "status": 200, + "ttfb_ms": 1309.1, + "total_ms": 1311.6, + "tls_ms": 82.5, + "after_connection_ms": 1226.6, + "ray": "a4b1b8ed599ff0a9-DFW", + "instance": "1b92c489-f902-4819-848c-8130e63712d4", + "instance_request": "1", + "phase_ms": "181", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 182, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 618.8, + "total_ms": 736.4, + "tls_ms": 82.7, + "after_connection_ms": 536.0, + "ray": "a4b1b8f8cebd7d9f-DFW", + "instance": "a6a9e844-914e-4fac-aa4c-de2e4ce1acb7", + "instance_request": "3", + "phase_ms": "0", + "server_timing": "app_headers;dur=58.0, db_config;dur=0.0, content_edge_read;dur=106.0, start_handler;dur=58.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 520, + "wall_ms": 5317, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b", + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "217bca47-2eb1-475e-8e4a-3988409b6a45", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1b8f8cebd7d9f", + "status": 200, + "headersReadyMs": 58, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 48 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 49 + }, + { + "name": "content_edge_read", + "startMs": 49, + "durationMs": 9 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 58 + } + ] + } + }, + { + "worker": "monolith", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 1340.1, + "total_ms": 1455.9, + "tls_ms": 78.5, + "after_connection_ms": 1261.6, + "ray": "a4b1b9009e8472f9-DFW", + "instance": "d3d3fd18-52fb-491d-8dc2-9f66d9647593", + "instance_request": "1", + "phase_ms": "0", + "server_timing": "app_headers;dur=59.0, db_config;dur=0.0, content_edge_read;dur=111.0, start_handler;dur=59.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 301, + "wall_ms": 361, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b", + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "f65874e1-20c8-48a1-8bdc-62eacfc988fa", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1b9009e8472f9", + "status": 200, + "headersReadyMs": 59, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 52 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 52 + }, + { + "name": "content_edge_read", + "startMs": 52, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 59 + } + ] + } + }, + { + "worker": "monolith", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 1124.6, + "total_ms": 1243.1, + "tls_ms": 84.4, + "after_connection_ms": 1040.1, + "ray": "a4b1b90cec306701-DFW", + "instance": "5158eb0a-4236-4509-8ff2-4a2e1bbd054d", + "instance_request": "3", + "phase_ms": "0", + "server_timing": "app_headers;dur=100.0, db_config;dur=0.0, content_edge_read;dur=184.0, start_handler;dur=100.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 854, + "wall_ms": 935, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b", + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "ee8c12e6-8e07-4418-8fa7-6a0dc5c0cf4d", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1b90cec306701", + "status": 200, + "headersReadyMs": 100, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 84 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 88 + }, + { + "name": "content_edge_read", + "startMs": 88, + "durationMs": 12 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 100 + } + ] + } + }, + { + "worker": "monolith", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 389.0, + "total_ms": 507.0, + "tls_ms": 85.7, + "after_connection_ms": 303.3, + "ray": "a4b1b917eb806a08-DFW", + "instance": "5158eb0a-4236-4509-8ff2-4a2e1bbd054d", + "instance_request": "4", + "phase_ms": "0", + "server_timing": "app_headers;dur=17.0, db_config;dur=0.0, content_edge_read;dur=28.0, start_handler;dur=17.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "cpu_ms": 197, + "wall_ms": 220, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b", + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "ee8c12e6-8e07-4418-8fa7-6a0dc5c0cf4d", + "docsRequest": 2, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1b917eb806a08", + "status": 200, + "headersReadyMs": 17, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 10 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 11 + }, + { + "name": "content_edge_read", + "startMs": 10, + "durationMs": 7 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 17 + } + ] + } + }, + { + "worker": "monolith", + "run": "initial", + "path": "/docs", + "status": 200, + "ttfb_ms": 2686.4, + "total_ms": 2807.6, + "tls_ms": 84.8, + "after_connection_ms": 2601.5, + "ray": "a4b1b91e5cae466b-DFW", + "instance": "8e1e845c-74ac-44b1-abf4-6156c15fd840", + "instance_request": "1", + "phase_ms": "0", + "server_timing": "app_headers;dur=102.0, db_config;dur=0.0, content_edge_read;dur=188.0, start_handler;dur=102.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "cpu_ms": 666, + "wall_ms": 768, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b", + "docs_timing": { + "logType": "docs_timing", + "workerInstance": "025218ac-39f6-4a39-8036-3b1ff140b450", + "docsRequest": 1, + "phase": "headers_ready", + "pathname": "/router/latest/docs/routing/routing-concepts", + "ray": "a4b1b91e5cae466b", + "status": 200, + "headersReadyMs": 102, + "databaseUsed": false, + "phases": [ + { + "name": "db_config", + "startMs": 0, + "durationMs": 0 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 86 + }, + { + "name": "content_edge_read", + "startMs": 0, + "durationMs": 88 + }, + { + "name": "content_edge_read", + "startMs": 88, + "durationMs": 14 + }, + { + "name": "start_handler", + "startMs": 0, + "durationMs": 102 + } + ] + } + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 157.4, + "total_ms": 157.9, + "tls_ms": 82.9, + "after_connection_ms": 74.3, + "ray": "a4b1b9332d1c6dd6-DFW", + "instance": "e95d8ca5-e614-472b-b454-a5252a0b65ca", + "instance_request": "3", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 0, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + }, + { + "worker": "monolith", + "run": "initial", + "path": "/noop", + "status": 200, + "ttfb_ms": 172.8, + "total_ms": 175.1, + "tls_ms": 85.0, + "after_connection_ms": 87.7, + "ray": "a4b1b937687d46d7-DFW", + "instance": "8e1e845c-74ac-44b1-abf4-6156c15fd840", + "instance_request": "2", + "phase_ms": "0", + "server_timing": null, + "cpu_ms": 0, + "wall_ms": 1, + "version": "8934b2f8-bf8c-40f2-bc46-eb37dd4c059b" + } + ] + } + } + ], + "largest_server_chunks": [ + { + "file": "ds.iconography.lazy-CQF6TLzF.js", + "bytes": 3804141, + "source_count": 2592, + "selected_sources": [ + "../../../src/components/ds/phosphor-icons.generated.ts", + "../../../src/routes/ds.iconography.lazy.tsx" + ] + }, + { + "file": "takumi_wasm_bg-MflJu3FC.wasm", + "bytes": 3734240, + "source_count": null, + "selected_sources": [] + }, + { + "file": "ai-QFQlMoNQ.js", + "bytes": 1899843, + "source_count": 1, + "selected_sources": [ + "../../../node_modules/.pnpm/@tanstack+create@0.70.1/node_modules/@tanstack/create/dist/generated/worker/frameworks/react/add-ons/ai.js" + ] + }, + { + "file": "App-CceFJ2uL.js", + "bytes": 1232478, + "source_count": 812, + "selected_sources": [ + "../../../src/chat/components/useUrlMessageFocus.ts", + "../../../src/chat/components/ConversationView.tsx", + "../../../src/chat/components/useNavigatorResize.ts", + "../../../src/chat/components/ConversationNavigator.tsx", + "../../../src/chat/components/CommandPalette.tsx", + "../../../src/chat/components/appearance-account.ts", + "../../../src/chat/components/WorkspaceCommands.tsx", + "../../../src/chat/components/SettingsFocusSection.tsx", + "../../../src/chat/components/Onboarding.tsx", + "../../../src/chat/components/bootstrap-onboarding.ts", + "../../../src/chat/components/AppearanceSettings.tsx", + "../../../src/chat/components/App.tsx" + ] + }, + { + "file": "run-models-C5RraMbZ.js", + "bytes": 1013948, + "source_count": 318, + "selected_sources": [ + "../../../src/chat/core/routing.ts", + "../../../src/chat/server/model-attachments.ts", + "../../../src/chat/server/run-models.ts" + ] + }, + { + "file": "blog-DfBvCpTe.js", + "bytes": 841491, + "source_count": 3, + "selected_sources": [ + "../../../src/utils/blog-format.ts", + "../../../.content-collections/generated/allPosts.js", + "../../../src/utils/blog.ts" + ] + }, + { + "file": "conversation-CfC_FNYP.js", + "bytes": 689033, + "source_count": 144, + "selected_sources": [ + "../../../src/chat/server/provider-observation.ts", + "../../../src/chat/server/providers.ts", + "../../../src/chat/server/discovery-model.ts", + "../../../src/chat/server/jev-router.ts", + "../../../src/chat/core/answers.ts", + "../../../src/chat/core/research-loop.ts", + "../../../src/chat/server/research.ts", + "../../../src/chat/server/provider-token-totals.ts", + "../../../src/chat/server/usage-cost.ts", + "../../../src/chat/server/usage.ts", + "../../../src/chat/server/kody-setup-readiness.ts", + "../../../src/chat/server/conversation.ts" + ] + }, + { + "file": "discovery-integrations-9BcnTPsE.js", + "bytes": 591551, + "source_count": 96, + "selected_sources": [ + "../../../node_modules/.pnpm/@babel+types@7.29.8/node_modules/@babel/types/lib/validators/generated/index.js", + "../../../node_modules/.pnpm/@babel+types@7.29.8/node_modules/@babel/types/lib/builders/generated/lowercase.js", + "../../../node_modules/.pnpm/@babel+types@7.29.8/node_modules/@babel/types/lib/builders/generated/uppercase.js", + "../../../node_modules/.pnpm/@babel+types@7.29.8/node_modules/@babel/types/lib/builders/generated/index.js", + "../../../node_modules/.pnpm/@babel+types@7.29.8/node_modules/@babel/types/lib/asserts/generated/index.js", + "../../../node_modules/.pnpm/@babel+types@7.29.8/node_modules/@babel/types/lib/constants/generated/index.js", + "../../../src/chat/server/kody-actions.ts", + "../../../src/chat/server/type-contract.ts", + "../../../src/chat/server/discovery-integrations/kody-contract.ts", + "../../../src/chat/server/discovery-integrations/contract.ts", + "../../../src/chat/server/discovery-integrations/kody.ts", + "../../../src/chat/server/discovery-integrations/index.ts" + ] + }, + { + "file": "sdk-DVisKDdM.js", + "bytes": 522967, + "source_count": 435, + "selected_sources": [ + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-tedious@0.33.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-tedious/build/src/instrumentation.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-tedious@0.33.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-tedious/build/src/index.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-generic-pool@0.57.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-generic-pool/build/src/version.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-generic-pool@0.57.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-generic-pool/build/src/instrumentation.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-generic-pool@0.57.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-generic-pool/build/src/index.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-amqplib@0.61.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-amqplib/build/src/semconv.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-amqplib@0.61.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-amqplib/build/src/semconv-obsolete.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-amqplib@0.61.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-amqplib/build/src/types.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-amqplib@0.61.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-amqplib/build/src/utils.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-amqplib@0.61.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-amqplib/build/src/version.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-amqplib@0.61.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-amqplib/build/src/amqplib.js", + "../../../node_modules/.pnpm/@opentelemetry+instrumentation-amqplib@0.61.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-amqplib/build/src/index.js" + ] + }, + { + "file": "chunk-K5T4RW27-CjFykgqp.js", + "bytes": 481708, + "source_count": 176, + "selected_sources": [ + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/lang/lang_extensions.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/cst/cst_visitor.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/tree_builder.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/lexer_adapter.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/recognizer_api.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/recognizer_engine.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/error_handler.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/context_assist.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/gast_recorder.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/traits/perf_tracer.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/utils/apply_mixins.js", + "../../../node_modules/.pnpm/chevrotain@11.1.2/node_modules/chevrotain/lib/src/parse/parser/parser.js" + ] + }, + { + "file": "server-BP6igsKL.js", + "bytes": 447071, + "source_count": 256, + "selected_sources": [] + }, + { + "file": "cytoscape.esm-j_LpqIHe.js", + "bytes": 438713, + "source_count": 1, + "selected_sources": [] + } + ], + "local_requests": [ + { + "label": "first", + "status": 200, + "headersMs": 134.701333, + "totalMs": 141.52879199999998, + "bytes": 359590, + "timing": "app_headers;dur=112.0, db_config;dur=1.0, content_edge_read;dur=12.0, start_handler;dur=108.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "samples": 953 + }, + { + "label": "repeat", + "status": 200, + "headersMs": 23.98745800000006, + "totalMs": 26.91979200000003, + "bytes": 357139, + "timing": "app_headers;dur=15.0, db_config;dur=0.0, content_edge_read;dur=2.0, start_handler;dur=14.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "samples": 653 + } + ], + "local_cpu_attribution": { + "first": { + "activeMs": 162.52, + "idleMs": 198.15, + "functions": [ + { + "source": "runtime", + "name": "(program)", + "self_ms": 27.77 + }, + { + "source": "runtime", + "name": "run", + "self_ms": 15.55 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "Tt", + "line": 324, + "self_ms": 8.52 + }, + { + "source": "../../../src/utils/partner-placement.ts", + "name": "F", + "line": 101, + "self_ms": 7.4 + }, + { + "source": "runtime", + "name": "text", + "self_ms": 5.8 + }, + { + "source": "assets/blog-DfBvCpTe.js", + "name": "", + "self_ms": 5.37 + }, + { + "source": "runtime", + "name": "(garbage collector)", + "self_ms": 4.78 + }, + { + "source": "assets/rolldown-runtime-DwyjGMXk.js", + "name": "u", + "self_ms": 3.52 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/router-manifest.js", + "name": "Da", + "line": 13, + "self_ms": 3.48 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "gi", + "line": 269, + "self_ms": 3.19 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "ye", + "line": 68, + "self_ms": 2.42 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "ge", + "line": 167, + "self_ms": 2.06 + }, + { + "source": "runtime", + "name": "readdir", + "self_ms": 1.43 + }, + { + "source": "assets/rolldown-runtime-DwyjGMXk.js", + "name": "", + "self_ms": 1.41 + }, + { + "source": "../../../src/utils/request-timing.server.ts", + "name": "S", + "line": 90, + "self_ms": 1.41 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "", + "line": 1093, + "self_ms": 1.28 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "mn", + "line": 93, + "self_ms": 1.27 + }, + { + "source": "runtime", + "name": "put", + "self_ms": 1.23 + }, + { + "source": "assets/rolldown-runtime-DwyjGMXk.js", + "name": "P", + "self_ms": 0.9 + }, + { + "source": "assets/documents.server-nSmHm7yy.js", + "name": "", + "self_ms": 0.9 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "o", + "line": 245, + "self_ms": 0.8 + }, + { + "source": "runtime", + "name": "ReadableStream", + "self_ms": 0.78 + }, + { + "source": "../../../src/utils/docs.functions.ts?tss-serverfn-split", + "name": "", + "line": 362, + "self_ms": 0.76 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "V", + "line": 455, + "self_ms": 0.66 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "name": "server", + "line": 193, + "self_ms": 0.65 + }, + { + "source": "../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/core.js", + "name": "n", + "line": 6, + "self_ms": 0.64 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "a", + "line": 255, + "self_ms": 0.64 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "be", + "line": 7240, + "self_ms": 0.63 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "Om", + "line": 160, + "self_ms": 0.59 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/framework.js", + "name": "U", + "line": 2, + "self_ms": 0.59 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "_appendCharToCurrentCharacterToken", + "line": 442, + "self_ms": 0.58 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/patterns.js", + "name": "_", + "line": 4, + "self_ms": 0.57 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "we", + "line": 313, + "self_ms": 0.56 + }, + { + "source": "assets/github-content-cache.server-QuLP0RGF.js", + "name": "", + "self_ms": 0.53 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "Ut", + "line": 1070, + "self_ms": 0.52 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/transformStreamWithRouter.js", + "name": "G", + "line": 456, + "self_ms": 0.52 + }, + { + "source": "assets/router-6StV-lzu.js", + "name": "", + "self_ms": 0.51 + }, + { + "source": "../../../node_modules/.pnpm/isbot@5.1.37/node_modules/isbot/index.mjs", + "name": "Jm", + "line": 202, + "self_ms": 0.51 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/route.js", + "name": "Ji.init", + "line": 21, + "self_ms": 0.51 + }, + { + "source": "../../../src/utils/docsNavTabs.ts", + "name": "f", + "line": 31, + "self_ms": 0.5 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "Ht", + "line": 45, + "self_ms": 0.5 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "b", + "line": 471, + "self_ms": 0.47 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/element.js", + "name": "_t", + "line": 8, + "self_ms": 0.45 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/script.js", + "name": "C", + "line": 43, + "self_ms": 0.45 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "Ze", + "line": 351, + "self_ms": 0.44 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "re", + "line": 63, + "self_ms": 0.44 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "oe", + "line": 37, + "self_ms": 0.4 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "qa", + "line": 1121, + "self_ms": 0.39 + }, + { + "source": "assets/schemas-Ct7VXAhV.js", + "name": "", + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "getClassGroupId", + "line": 37, + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "c", + "line": 50, + "self_ms": 0.39 + }, + { + "source": "runtime", + "name": "match", + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "n", + "line": 463, + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/htmlBoundaryScanner.js", + "name": "Gs", + "line": 13, + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "_emitCodePoint", + "line": 456, + "self_ms": 0.39 + }, + { + "source": "runtime", + "name": "read", + "self_ms": 0.38 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+semantic-conventions@1.40.0/node_modules/@opentelemetry/semantic-conventions/build/esm/internal/utils.js", + "name": "E", + "line": 22, + "self_ms": 0.38 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "sl", + "line": 152, + "self_ms": 0.34 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "name": "K", + "line": 148, + "self_ms": 0.34 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "Mm", + "line": 298, + "self_ms": 0.33 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "name": "_", + "line": 2, + "self_ms": 0.33 + }, + { + "source": "../../../src/server/runtime/host.server.ts", + "name": "dn", + "line": 92, + "self_ms": 0.33 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/script.js", + "name": "ze", + "line": 282, + "self_ms": 0.29 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "mh", + "line": 2574, + "self_ms": 0.27 + }, + { + "source": "runtime", + "name": "", + "self_ms": 0.27 + }, + { + "source": "node-internal:internal_querystring", + "name": "", + "self_ms": 0.27 + }, + { + "source": "../../../src/server/runtime/blob-storage.server.ts", + "name": "c", + "line": 186, + "self_ms": 0.27 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "g", + "line": 128, + "self_ms": 0.27 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "yr", + "line": 589, + "self_ms": 0.26 + }, + { + "source": "assets/partners-BTN_k5aB.js", + "name": "", + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/partner-placement.ts", + "name": "R2", + "line": 121, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/router.js", + "name": "tp", + "line": 19, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/scope.js", + "name": "it", + "line": 76, + "self_ms": 0.26 + }, + { + "source": "runtime", + "name": "now", + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/markdown/filterFrameworkContent.ts", + "name": "D", + "line": 36, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/stream.js", + "name": "ac", + "line": 34, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/stream.js", + "name": "p", + "line": 253, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/common.js", + "name": "s", + "line": 60, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/load-server.js", + "name": "Mu", + "line": 403, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tree-adapters/default.js", + "name": "createElement", + "line": 17, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "F", + "line": 2321, + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/utils.ts", + "name": "N", + "line": 165, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/load-server.js", + "name": "Bu", + "line": 539, + "self_ms": 0.26 + }, + { + "source": "runtime", + "name": "Headers", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/compose-scalar.js", + "name": "", + "line": 71, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "name": "onCharacter", + "line": 635, + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "$a", + "line": 1326, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "", + "line": 855, + "self_ms": 0.25 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "Je", + "line": 1143, + "self_ms": 0.25 + }, + { + "source": "assets/dist-BZ4LQB0H.js", + "name": "", + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/createStartHandler.js", + "name": "", + "line": 274, + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "s", + "line": 251, + "self_ms": 0.25 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "", + "line": 1333, + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "name": "R", + "line": 117, + "self_ms": 0.24 + }, + { + "source": "../../../src/components/LibraryLayout.tsx", + "name": "", + "line": 997, + "self_ms": 0.21 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "N", + "line": 443, + "self_ms": 0.19 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/utils.js", + "name": "oe", + "line": 91, + "self_ms": 0.17 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/framework.js", + "name": "", + "line": 7, + "self_ms": 0.17 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "de", + "line": 20, + "self_ms": 0.17 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "ce", + "line": 437, + "self_ms": 0.16 + }, + { + "source": "../../../src/components/markdown/FrameworkContent.tsx", + "name": "lr", + "line": 21, + "self_ms": 0.16 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/dispatcher.js", + "name": "useMemo", + "line": 83, + "self_ms": 0.16 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/react.js", + "name": "f", + "line": 11, + "self_ms": 0.16 + }, + { + "source": "node-internal:internal_inspect", + "name": "formatWithOptions", + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/asyncContext/stackStrategy.js", + "name": "getScope", + "line": 68, + "self_ms": 0.14 + }, + { + "source": "../../../src/utils/partners.tsx", + "name": "_0", + "line": 175, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Os", + "line": 1939, + "self_ms": 0.14 + }, + { + "source": "../../src/utils/docs-html-headers.server.ts", + "name": "J", + "line": 5, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/request-response.js", + "name": "", + "line": 58, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/createStartHandler.js", + "name": "Jo", + "line": 26, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/headings.js", + "name": "V", + "line": 13, + "self_ms": 0.14 + }, + { + "source": "runtime", + "name": "URL", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "fu", + "line": 96, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/early-hints.js", + "name": "yo", + "line": 80, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/env.functions.ts", + "name": "_", + "line": 1, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/transformAssetUrls.js", + "name": "Oo", + "line": 153, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/common/unicode.js", + "name": "", + "line": 9, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "J", + "line": 2662, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-query@5.102.8_react@19.2.3/node_modules/@tanstack/react-query/build/modern/useBaseQuery.js", + "name": "React.useState", + "line": 27, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "buildLocation", + "line": 245, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-primitive@2.1.3_@types+react-dom@19.2.3_@types+react@19.2.14__@types+re_412fca877c3e29a339aeb07f39bb1abf/node_modules/@radix-ui/react-primitive/dist/index.mjs", + "name": "", + "line": 25, + "self_ms": 0.13 + }, + { + "source": "assets/schemas-DwJQzDBF.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/stream/GenericWorker.js", + "name": "", + "line": 263, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "entries", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "w", + "line": 146, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "RegExp: ^ {0,3}(?:`+|~+)\\s*$", + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "RegExp: [\\u0000-\\u001F\\u007F\\s]+", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/ssr-server.js", + "name": "li", + "line": 72, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-mysql@0.60.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-mysql/build/src/version.js", + "name": "", + "line": 21, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/partners.tsx", + "name": "h0", + "line": 1985, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "j", + "line": 404, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/deflate.js", + "name": "", + "line": 1865, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/route.js", + "name": "Hh", + "line": 93, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router-ssr-query@1.167.2_@tanstack+query-core@5.102.8_@tanstack+react-q_21713144354376ef39f15289cdf6f1a2/node_modules/@tanstack/react-router-ssr-query/dist/esm/index.js", + "name": "xg", + "line": 6, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs.ts", + "name": "q", + "line": 159, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "ya", + "line": 846, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "ae", + "line": 124, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/comment-components.js", + "name": "_", + "line": 7, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "yh", + "line": 2641, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-context@1.1.2_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-context/dist/index.mjs", + "name": "", + "line": 66, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "RegExp: [&<>]", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "name": "v", + "line": 142, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs.functions.ts?tss-serverfn-split", + "name": "node", + "line": 583, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "fetch", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/asyncContext/stackStrategy.js", + "name": "_pushScope", + "line": 89, + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_fs_sync", + "name": "readdirSync", + "self_ms": 0.13 + }, + { + "source": "cloudflare-internal:http", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-pg@0.66.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-pg/build/src/version.js", + "name": "", + "line": 21, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/schemas.js", + "name": "Va", + "line": 186, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "K", + "line": 1947, + "self_ms": 0.13 + }, + { + "source": "assets/TrafficSign.es-YCoTTu2w.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "te", + "line": 5244, + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/index.tsx", + "name": "edgeAnchor", + "line": 281, + "self_ms": 0.13 + }, + { + "source": "assets/docs-webhook-sources-DGwy3Y6E.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "name": "ye", + "line": 153, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/finalManifest.js", + "name": "r", + "line": 99, + "self_ms": 0.13 + }, + { + "source": "../../../src/libraries/libraries.ts", + "name": "X", + "line": 998, + "self_ms": 0.13 + }, + { + "source": "../../../src/queries/docsConfig.ts", + "name": "h", + "line": 17, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/lexer.js", + "name": "parseDocument", + "line": 317, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/doc/Document.js", + "name": "setSchema", + "line": 252, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "xe", + "line": 329, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "l", + "line": 237, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "w", + "line": 215, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "_runParsingLoop", + "line": 205, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-context@1.1.2_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-context/dist/index.mjs", + "name": "React.useMemo", + "line": 48, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "get", + "line": 204, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/sieve-cache.js", + "name": "set", + "line": 25, + "self_ms": 0.13 + }, + { + "source": "../../../src/components/LibraryLayout.tsx", + "name": "", + "line": 929, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "name": "React$1.forwardRef", + "line": 314, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/react.js", + "name": "v", + "line": 7, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "Nt", + "line": 1022, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryCache.js", + "name": "", + "line": 52, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/tracing/trace.js", + "name": "me.status", + "line": 72, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+api@1.9.1/node_modules/@opentelemetry/api/build/esm/diag-api.js", + "name": "", + "line": 16, + "self_ms": 0.13 + }, + { + "source": "assets/intent.functions-Ct5UaBoC.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "assets/blog-Bc_YYqxE.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "ze", + "line": 165, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "$e", + "line": 143, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/createStartHandler.js", + "name": "u", + "line": 172, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "qe", + "line": 170, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "c", + "line": 1297, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/public-api.js", + "name": "Tn", + "line": 38, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/lexer.js", + "name": "lex", + "line": 150, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/parser.js", + "name": "scalar", + "line": 428, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/compose-doc.js", + "name": "yn", + "line": 6, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js", + "name": "on", + "line": 41, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/callouts.js", + "name": "J", + "line": 7, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "name": "M", + "line": 21, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "name": "oe", + "line": 326, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tree-adapters/default.js", + "name": "insertText", + "line": 88, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/seo.ts", + "name": "w", + "line": 77, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/hydration.js", + "name": "Cs", + "line": 28, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/escape.js", + "name": "pr", + "line": 68, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryObserver.js", + "name": "", + "line": 291, + "self_ms": 0.13 + }, + { + "source": "../../../src/components/Navbar.tsx", + "name": "Ad", + "line": 81, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "RegExp: [&\"<>]", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/route.js", + "name": "Hh.useParams", + "line": 113, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/react.js", + "name": "b", + "line": 176, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "name": "c", + "line": 43, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/core/resource-hints.js", + "name": "Er", + "line": 11, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/transformStreamWithRouter.js", + "name": "Rr", + "line": 157, + "self_ms": 0.13 + }, + { + "source": "../../src/server.ts", + "name": "", + "line": 228, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "", + "line": 1157, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/utils/handleCallbackErrors.js", + "name": "", + "line": 58, + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_net", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-express@0.62.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-express/build/src/version.js", + "name": "", + "line": 21, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-redis@0.62.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-redis/build/src/version.js", + "name": "", + "line": 21, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "me", + "line": 7140, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/schemas.js", + "name": "e.check", + "line": 21, + "self_ms": 0.13 + }, + { + "source": "assets/match-sorter.esm-B_qnfP2X.js", + "name": "", + "line": null, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/lazyRouteComponent.js", + "name": "bg", + "line": 15, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "x", + "line": 635, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryClient.js", + "name": "Ts", + "line": 17, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/path.js", + "name": "Pi", + "line": 14, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/createStartHandler.js", + "name": "u", + "line": 483, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "matchRoutesLightweight", + "line": 778, + "self_ms": 0.13 + }, + { + "source": "assets/ArrowsOutLineHorizontal.es-CUEx7LoN.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "H", + "line": 3215, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/query.js", + "name": "fetch", + "line": 156, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "name": "Object.assign", + "line": 45, + "self_ms": 0.13 + }, + { + "source": "assets/browser-U4zUUJM6.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "Vt", + "line": 1369, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/config.ts?tss-serverfn-split", + "name": "parseDocsConfig", + "line": 74, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/resolve-block-map.js", + "name": "zs", + "line": 9, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "va", + "line": 912, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "name": "H", + "line": 319, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "", + "line": 319, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/live-example.ts", + "name": "re", + "line": 209, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "name": "getFragmentParser", + "line": 119, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "name": "getFragment", + "line": 141, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "Ga", + "line": 1027, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/load-server.js", + "name": "Fu", + "line": 343, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/load-server.js", + "name": "Nu", + "line": 371, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Br", + "line": 1179, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "as", + "line": 37, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/resource-hints.js", + "name": "addHost", + "line": 69, + "self_ms": 0.13 + }, + { + "source": "../../../src/contexts/SearchContext.tsx", + "name": "", + "line": 119, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/escape.js", + "name": "_m", + "line": 131, + "self_ms": 0.13 + }, + { + "source": "../../../src/components/Navbar.tsx", + "name": "", + "line": 383, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "Rn", + "line": 285, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/removable.js", + "name": "scheduleGc", + "line": 10, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/useMatch.js", + "name": "je", + "line": 22, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docsNavTabs.ts", + "name": "c", + "line": 99, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/code-fence.js", + "name": "$", + "line": 77, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/script.js", + "name": "y", + "line": 37, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/stream.js", + "name": "Um", + "line": 188, + "self_ms": 0.13 + }, + { + "source": "../../src/utils/prod-diagnostics.server.ts", + "name": "c", + "line": 79, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/request-timing.server.ts", + "name": "pull", + "line": 106, + "self_ms": 0.13 + }, + { + "source": "assets/esm-CmcjpCbh.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "assets/esm-Ce-Mi024.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_zlib_base", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+api@1.9.1/node_modules/@opentelemetry/api/build/esm/internal/global-utils.js", + "name": "", + "line": 53, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/supports-color@5.5.0/node_modules/supports-color/index.js", + "name": "f", + "line": 122, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/utils/misc.js", + "name": "lr", + "line": 127, + "self_ms": 0.13 + }, + { + "source": "assets/dist-CCiojUqn.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "ce", + "line": 6385, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "E", + "line": 541, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/path.js", + "name": "hs", + "line": 119, + "self_ms": 0.13 + }, + { + "source": "assets/dist-CJf1wIqo.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/zustand@5.0.12_@types+react@19.2.14_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/vanilla.mjs", + "name": "l", + "line": 1, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryClient.js", + "name": "ensureQueryData", + "line": 75, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/documents.server.ts", + "name": "W", + "line": 1197, + "self_ms": 0.13 + }, + { + "source": "../../src/utils/prod-diagnostics.server.ts", + "name": "t", + "line": 113, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/parser.js", + "name": "next", + "line": 173, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/parser.js", + "name": "blockMap", + "line": 479, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/composer.js", + "name": "compose", + "line": 129, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/public-api.js", + "name": "En", + "line": 58, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/docs.js", + "name": "X", + "line": 6, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "RegExp: ^\\s*$", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "ce", + "line": 134, + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/_library/$libraryId/$version.docs.tsx", + "name": "headers", + "line": 23, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs-embed-headers.ts", + "name": "x", + "line": 12, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Fr", + "line": 400, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/headContentUtils.js", + "name": "qs", + "line": 7, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/path.js", + "name": "ds", + "line": 45, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryObserver.js", + "name": "createResult", + "line": 182, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/script.js", + "name": "ye", + "line": 196, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-ssr-query-core@1.169.2_@tanstack+query-core@5.102.8_@tanstack+router-core@1.171.34/node_modules/@tanstack/router-ssr-query-core/dist/esm/index.js", + "name": "_", + "line": 86, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-start@1.168.60_esbuild@0.27.4_react-dom@19.2.3_react@19.2.3__react@19.2_b22d3cffd4a0ac63d50c71211c37abf6/node_modules/@tanstack/react-start/dist/default-entry/esm/server.js", + "name": "fetch", + "line": 5, + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_fs_utils", + "name": "validateReaddirArgs", + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_http_agent", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+core@2.6.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/core/build/esm/utils/timeout.js", + "name": "", + "line": 39, + "self_ms": 0.13 + }, + { + "source": "assets/sdk-DVisKDdM.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-http@0.214.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-http/build/src/internal-types.js", + "name": "", + "line": 28, + "self_ms": 0.13 + }, + { + "source": "assets/auth.functions-BstwcUzq.js", + "name": "", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "_e", + "line": 116, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "get process", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "getMatchedRoutes", + "line": 235, + "self_ms": 0.13 + }, + { + "source": "assets/_tanstack-start-manifest_v-BBqkB58P.js", + "name": "s", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryClient.js", + "name": "fetchQuery", + "line": 171, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs-redirects.ts", + "name": "U", + "line": 29, + "self_ms": 0.13 + }, + { + "source": "runtime", + "name": "getStore", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Jd", + "line": 2357, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-start-server@1.167.46_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-start-server/dist/esm/defaultStreamHandler.js", + "name": "", + "line": 5, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/escape.js", + "name": "hr", + "line": 20, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "name": "wa", + "line": 211, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "Vc", + "line": 229, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/handlerCallback.js", + "name": "ja", + "line": 2, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/tracing/trace.js", + "name": "Yr", + "line": 39, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/utils/misc.js", + "name": "I", + "line": 22, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-mongoose@0.60.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-mongoose/build/src/version.js", + "name": "", + "line": 21, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/theme.js", + "name": "o", + "line": 51, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+history@1.162.4/node_modules/@tanstack/history/dist/esm/index.js", + "name": "zi", + "line": 446, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "Yc", + "line": 368, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "name": "~run", + "line": 5390, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/headContentUtils.js", + "name": "", + "line": 101, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/Asset.js", + "name": "Ca", + "line": 14, + "self_ms": 0.13 + }, + { + "source": "../../../src/components/ToastProvider.tsx", + "name": "$e", + "line": 24, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/children.js", + "name": "", + "line": 61, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-context@1.1.2_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-context/dist/index.mjs", + "name": "React.useMemo", + "line": 71, + "self_ms": 0.13 + }, + { + "source": "../../../src/components/Footer.tsx", + "name": "v", + "line": 127, + "self_ms": 0.13 + }, + { + "source": "../../../src/components/Footer.tsx", + "name": "s", + "line": 106, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/sonner@2.0.7_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/sonner/dist/index.mjs", + "name": "", + "line": 916, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "x", + "line": 606, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/zipObject.js", + "name": "", + "line": 133, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/createStartHandler.js", + "name": "H", + "line": 350, + "self_ms": 0.12 + }, + { + "source": "../../../src/server/runtime/blob-storage.server.ts", + "name": "b", + "line": 272, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "name": "getConflictingClassGroupIds", + "line": 46, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "name": "Sa", + "line": 34, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/core/attributes.js", + "name": "Ja", + "line": 7, + "self_ms": 0.12 + }, + { + "source": "../../../src/components/MegaMenuItem.tsx", + "name": "M", + "line": 39, + "self_ms": 0.12 + }, + { + "source": "../../../src/utils/docsNavTabs.ts", + "name": "d", + "line": 156, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "name": "zh", + "line": 243, + "self_ms": 0.12 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "Bt", + "line": 1227, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryObserver.js", + "name": "setOptions", + "line": 58, + "self_ms": 0.12 + }, + { + "source": "../../../src/components/LibraryLayout.tsx", + "name": "Xt", + "line": 663, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+core@2.6.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/core/build/esm/common/global-error-handler.js", + "name": "", + "line": 24, + "self_ms": 0.12 + }, + { + "source": "../../../src/ui/Tooltip.tsx", + "name": "Be", + "line": 14, + "self_ms": 0.12 + }, + { + "source": "../../../src/components/Select.tsx", + "name": "K", + "line": 28, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/zustand@5.0.12_@types+react@19.2.14_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/react.mjs", + "name": "I", + "line": 5, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/hydrationScripts.js", + "name": "Fp", + "line": 36, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "da", + "line": 2696, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/route.js", + "name": "Uh.useRouteContext", + "line": 187, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/lru-cache@11.2.7/node_modules/lru-cache/dist/esm/index.min.js", + "name": "j", + "line": 1, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "oa", + "line": 1618, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/memo.js", + "name": "_init", + "line": 19, + "self_ms": 0.12 + }, + { + "source": "../../../src/utils/partners.tsx", + "name": "F0", + "line": 2261, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/ssr/renderRouterToStream.js", + "name": "zg", + "line": 6, + "self_ms": 0.12 + }, + { + "source": "../../../src/components/ds/ui/PartnerTierLogo.tsx", + "name": "k", + "line": 44, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/request-response.js", + "name": "", + "line": 45, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "_t", + "line": 756, + "self_ms": 0.11 + }, + { + "source": "assets/builtin_esm-external-require-events-BxdsvtrH.js", + "name": "", + "self_ms": 0.11 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "matchRoutes", + "line": 228, + "self_ms": 0.11 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "parseBlocks", + "line": 93, + "self_ms": 0.11 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/escape.js", + "name": "Xa", + "line": 3, + "self_ms": 0.1 + }, + { + "source": "../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@phosphor-icons/react/dist/lib/IconBase.es.js", + "name": "e.forwardRef", + "line": 3, + "self_ms": 0.1 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Mr", + "line": 838, + "self_ms": 0.1 + }, + { + "source": "../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@phosphor-icons/react/dist/csr/LinkedinLogo.es.js", + "name": "o.forwardRef", + "line": 4, + "self_ms": 0.09 + }, + { + "source": "../../../src/components/VersionSelect.tsx", + "name": "ke", + "line": 12, + "self_ms": 0.09 + }, + { + "source": "../../../src/server/runtime/blob-storage.server.ts", + "name": "g", + "line": 145, + "self_ms": 0.09 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/common/unicode.js", + "name": "it", + "line": 46, + "self_ms": 0.09 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tree-adapters/default.js", + "name": "appendChild", + "line": 42, + "self_ms": 0.09 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "Hc", + "line": 15, + "self_ms": 0.09 + }, + { + "source": "../../../node_modules/.pnpm/sonner@2.0.7_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/sonner/dist/index.mjs", + "name": "pe", + "line": 123, + "self_ms": 0.09 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/core/resource-hints.js", + "name": "Qa", + "line": 3, + "self_ms": 0.07 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/early-hints.js", + "name": "j", + "line": 12, + "self_ms": 0.07 + }, + { + "source": "../../../src/hooks/useCurrentUser.ts", + "name": "select", + "line": 15, + "self_ms": 0.07 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/hydrationScripts.js", + "name": "clearTimeoutIfSet", + "line": 117, + "self_ms": 0.06 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/route.js", + "name": "get id", + "line": 11, + "self_ms": 0.06 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/jsx-runtime.js", + "name": "le", + "line": 4, + "self_ms": 0.05 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "a", + "line": 304, + "self_ms": 0.05 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/Asset.js", + "name": "ip", + "line": 68, + "self_ms": 0.05 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "write", + "line": 235, + "self_ms": 0.04 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "name": "Xs", + "line": 60, + "self_ms": 0.04 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/route.js", + "name": "get fullPath", + "line": 17, + "self_ms": 0.02 + } + ], + "sources": [ + { + "source": "runtime", + "self_ms": 60.19 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "self_ms": 9.82 + }, + { + "source": "../../../src/utils/partner-placement.ts", + "self_ms": 7.66 + }, + { + "source": "../../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs", + "self_ms": 6.06 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "self_ms": 6 + }, + { + "source": "assets/rolldown-runtime-DwyjGMXk.js", + "self_ms": 5.83 + }, + { + "source": "assets/blog-DfBvCpTe.js", + "self_ms": 5.37 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "self_ms": 4.16 + }, + { + "source": "../../../src/utils/documents.server.ts", + "self_ms": 3.59 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/router-manifest.js", + "self_ms": 3.48 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "self_ms": 2.07 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "self_ms": 1.74 + }, + { + "source": "../../../src/utils/request-timing.server.ts", + "self_ms": 1.54 + }, + { + "source": "../../../node_modules/.pnpm/valibot@1.3.1_typescript@6.0.2/node_modules/valibot/dist/index.mjs", + "self_ms": 1.53 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "self_ms": 1.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "self_ms": 1.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "self_ms": 1.08 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/script.js", + "self_ms": 0.99 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "self_ms": 0.91 + }, + { + "source": "assets/documents.server-nSmHm7yy.js", + "self_ms": 0.9 + }, + { + "source": "../../../src/utils/docs.functions.ts?tss-serverfn-split", + "self_ms": 0.89 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "self_ms": 0.83 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/createStartHandler.js", + "self_ms": 0.77 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/load-server.js", + "self_ms": 0.77 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/framework.js", + "self_ms": 0.77 + }, + { + "source": "../../../src/utils/docsNavTabs.ts", + "self_ms": 0.76 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "self_ms": 0.71 + }, + { + "source": "../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/core.js", + "self_ms": 0.64 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/stream.js", + "self_ms": 0.64 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/transformStreamWithRouter.js", + "self_ms": 0.64 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/route.js", + "self_ms": 0.59 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/patterns.js", + "self_ms": 0.57 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "self_ms": 0.55 + }, + { + "source": "assets/github-content-cache.server-QuLP0RGF.js", + "self_ms": 0.53 + }, + { + "source": "assets/router-6StV-lzu.js", + "self_ms": 0.51 + }, + { + "source": "../../../node_modules/.pnpm/isbot@5.1.37/node_modules/isbot/index.mjs", + "self_ms": 0.51 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "self_ms": 0.5 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/escape.js", + "self_ms": 0.49 + }, + { + "source": "../../../src/server/runtime/blob-storage.server.ts", + "self_ms": 0.48 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tree-adapters/default.js", + "self_ms": 0.47 + }, + { + "source": "../../../src/components/LibraryLayout.tsx", + "self_ms": 0.46 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/element.js", + "self_ms": 0.45 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/react.js", + "self_ms": 0.42 + }, + { + "source": "../../../src/utils/partners.tsx", + "self_ms": 0.39 + }, + { + "source": "assets/schemas-Ct7VXAhV.js", + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-context@1.1.2_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-context/dist/index.mjs", + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/htmlBoundaryScanner.js", + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/parser.js", + "self_ms": 0.38 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/path.js", + "self_ms": 0.38 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/route.js", + "self_ms": 0.38 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryClient.js", + "self_ms": 0.38 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+semantic-conventions@1.40.0/node_modules/@opentelemetry/semantic-conventions/build/esm/internal/utils.js", + "self_ms": 0.38 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryObserver.js", + "self_ms": 0.38 + }, + { + "source": "../../../src/server/runtime/host.server.ts", + "self_ms": 0.33 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/asyncContext/stackStrategy.js", + "self_ms": 0.27 + }, + { + "source": "node-internal:internal_querystring", + "self_ms": 0.27 + }, + { + "source": "assets/partners-BTN_k5aB.js", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/router.js", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/scope.js", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/lexer.js", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/schemas.js", + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/markdown/filterFrameworkContent.ts", + "self_ms": 0.26 + }, + { + "source": "../../../src/components/Navbar.tsx", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/common.js", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/public-api.js", + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/utils.ts", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/tracing/trace.js", + "self_ms": 0.26 + }, + { + "source": "../../src/utils/prod-diagnostics.server.ts", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/compose-scalar.js", + "self_ms": 0.26 + }, + { + "source": "assets/dist-BZ4LQB0H.js", + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/utils/misc.js", + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/request-response.js", + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/headContentUtils.js", + "self_ms": 0.25 + }, + { + "source": "../../../src/components/Footer.tsx", + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/common/unicode.js", + "self_ms": 0.22 + }, + { + "source": "../../../node_modules/.pnpm/sonner@2.0.7_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/sonner/dist/index.mjs", + "self_ms": 0.21 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/early-hints.js", + "self_ms": 0.21 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/core/resource-hints.js", + "self_ms": 0.2 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/hydrationScripts.js", + "self_ms": 0.18 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/Asset.js", + "self_ms": 0.17 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/utils.js", + "self_ms": 0.17 + }, + { + "source": "../../../src/components/markdown/FrameworkContent.tsx", + "self_ms": 0.16 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/dispatcher.js", + "self_ms": 0.16 + }, + { + "source": "node-internal:internal_inspect", + "self_ms": 0.14 + }, + { + "source": "../../src/utils/docs-html-headers.server.ts", + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/headings.js", + "self_ms": 0.14 + }, + { + "source": "../../../src/utils/env.functions.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/transformAssetUrls.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-query@5.102.8_react@19.2.3/node_modules/@tanstack/react-query/build/modern/useBaseQuery.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-primitive@2.1.3_@types+react-dom@19.2.3_@types+react@19.2.14__@types+re_412fca877c3e29a339aeb07f39bb1abf/node_modules/@radix-ui/react-primitive/dist/index.mjs", + "self_ms": 0.13 + }, + { + "source": "assets/schemas-DwJQzDBF.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/stream/GenericWorker.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/ssr-server.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-mysql@0.60.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-mysql/build/src/version.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/deflate.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router-ssr-query@1.167.2_@tanstack+query-core@5.102.8_@tanstack+react-q_21713144354376ef39f15289cdf6f1a2/node_modules/@tanstack/react-router-ssr-query/dist/esm/index.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/comment-components.js", + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_fs_sync", + "self_ms": 0.13 + }, + { + "source": "cloudflare-internal:http", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-pg@0.66.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-pg/build/src/version.js", + "self_ms": 0.13 + }, + { + "source": "assets/TrafficSign.es-YCoTTu2w.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/index.tsx", + "self_ms": 0.13 + }, + { + "source": "assets/docs-webhook-sources-DGwy3Y6E.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-server-core@1.169.39/node_modules/@tanstack/start-server-core/dist/esm/finalManifest.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/libraries/libraries.ts", + "self_ms": 0.13 + }, + { + "source": "../../../src/queries/docsConfig.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/doc/Document.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/sieve-cache.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryCache.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+api@1.9.1/node_modules/@opentelemetry/api/build/esm/diag-api.js", + "self_ms": 0.13 + }, + { + "source": "assets/intent.functions-Ct5UaBoC.js", + "self_ms": 0.13 + }, + { + "source": "assets/blog-Bc_YYqxE.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/compose-doc.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/callouts.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/seo.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/hydration.js", + "self_ms": 0.13 + }, + { + "source": "../../src/server.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@sentry+core@10.47.0/node_modules/@sentry/core/build/esm/utils/handleCallbackErrors.js", + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_net", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-express@0.62.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-express/build/src/version.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-redis@0.62.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-redis/build/src/version.js", + "self_ms": 0.13 + }, + { + "source": "assets/match-sorter.esm-B_qnfP2X.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/lazyRouteComponent.js", + "self_ms": 0.13 + }, + { + "source": "assets/ArrowsOutLineHorizontal.es-CUEx7LoN.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/query.js", + "self_ms": 0.13 + }, + { + "source": "assets/browser-U4zUUJM6.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/config.ts?tss-serverfn-split", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/resolve-block-map.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/live-example.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/resource-hints.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/contexts/SearchContext.tsx", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/removable.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/useMatch.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/code-fence.js", + "self_ms": 0.13 + }, + { + "source": "assets/esm-CmcjpCbh.js", + "self_ms": 0.13 + }, + { + "source": "assets/esm-Ce-Mi024.js", + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_zlib_base", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+api@1.9.1/node_modules/@opentelemetry/api/build/esm/internal/global-utils.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/supports-color@5.5.0/node_modules/supports-color/index.js", + "self_ms": 0.13 + }, + { + "source": "assets/dist-CCiojUqn.js", + "self_ms": 0.13 + }, + { + "source": "assets/dist-CJf1wIqo.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/zustand@5.0.12_@types+react@19.2.14_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/vanilla.mjs", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/compose/composer.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/docs.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/_library/$libraryId/$version.docs.tsx", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs-embed-headers.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-ssr-query-core@1.169.2_@tanstack+query-core@5.102.8_@tanstack+router-core@1.171.34/node_modules/@tanstack/router-ssr-query-core/dist/esm/index.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-start@1.168.60_esbuild@0.27.4_react-dom@19.2.3_react@19.2.3__react@19.2_b22d3cffd4a0ac63d50c71211c37abf6/node_modules/@tanstack/react-start/dist/default-entry/esm/server.js", + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_fs_utils", + "self_ms": 0.13 + }, + { + "source": "node-internal:internal_http_agent", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+core@2.6.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/core/build/esm/utils/timeout.js", + "self_ms": 0.13 + }, + { + "source": "assets/sdk-DVisKDdM.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-http@0.214.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-http/build/src/internal-types.js", + "self_ms": 0.13 + }, + { + "source": "assets/auth.functions-BstwcUzq.js", + "self_ms": 0.13 + }, + { + "source": "assets/_tanstack-start-manifest_v-BBqkB58P.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs-redirects.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-start-server@1.167.46_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-start-server/dist/esm/defaultStreamHandler.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/handlerCallback.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+instrumentation-mongoose@0.60.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/instrumentation-mongoose/build/src/version.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/theme.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+history@1.162.4/node_modules/@tanstack/history/dist/esm/index.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/components/ToastProvider.tsx", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/children.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/zipObject.js", + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/core/attributes.js", + "self_ms": 0.12 + }, + { + "source": "../../../src/components/MegaMenuItem.tsx", + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@opentelemetry+core@2.6.1_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/core/build/esm/common/global-error-handler.js", + "self_ms": 0.12 + }, + { + "source": "../../../src/ui/Tooltip.tsx", + "self_ms": 0.12 + }, + { + "source": "../../../src/components/Select.tsx", + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/zustand@5.0.12_@types+react@19.2.14_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/react.mjs", + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/lru-cache@11.2.7/node_modules/lru-cache/dist/esm/index.min.js", + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/memo.js", + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/ssr/renderRouterToStream.js", + "self_ms": 0.12 + }, + { + "source": "../../../src/components/ds/ui/PartnerTierLogo.tsx", + "self_ms": 0.12 + }, + { + "source": "assets/builtin_esm-external-require-events-BxdsvtrH.js", + "self_ms": 0.11 + }, + { + "source": "../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@phosphor-icons/react/dist/lib/IconBase.es.js", + "self_ms": 0.1 + }, + { + "source": "../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@phosphor-icons/react/dist/csr/LinkedinLogo.es.js", + "self_ms": 0.09 + }, + { + "source": "../../../src/components/VersionSelect.tsx", + "self_ms": 0.09 + }, + { + "source": "../../../src/hooks/useCurrentUser.ts", + "self_ms": 0.07 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/react/jsx-runtime.js", + "self_ms": 0.05 + } + ] + }, + "repeat": { + "activeMs": 25.44, + "idleMs": 28.13, + "functions": [ + { + "source": "runtime", + "name": "(garbage collector)", + "self_ms": 5.71 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/subscribable.js", + "name": "", + "line": 10, + "self_ms": 2.89 + }, + { + "source": "runtime", + "name": "get process", + "self_ms": 1.9 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "name": "Tt", + "line": 324, + "self_ms": 1.43 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "ge", + "line": 167, + "self_ms": 1.32 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "re", + "line": 63, + "self_ms": 0.54 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "Om", + "line": 160, + "self_ms": 0.47 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "mn", + "line": 93, + "self_ms": 0.43 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/stream.js", + "name": "p", + "line": 253, + "self_ms": 0.39 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/htmlBoundaryScanner.js", + "name": "Gs", + "line": 13, + "self_ms": 0.39 + }, + { + "source": "../../../src/utils/docsNavTabs.ts", + "name": "f", + "line": 31, + "self_ms": 0.37 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/script.js", + "name": "C", + "line": 43, + "self_ms": 0.34 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "Mm", + "line": 298, + "self_ms": 0.34 + }, + { + "source": "../../src/server.ts", + "name": "", + "line": 228, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "name": "a", + "line": 156, + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/request-timing.server.ts", + "name": "S", + "line": 90, + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/stream.js", + "name": "ac", + "line": 34, + "self_ms": 0.25 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "c", + "line": 50, + "self_ms": 0.2 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "name": "P", + "line": 246, + "self_ms": 0.19 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/react.js", + "name": "b", + "line": 176, + "self_ms": 0.18 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "N", + "line": 443, + "self_ms": 0.17 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryObserver.js", + "name": "getOptimisticResult", + "line": 77, + "self_ms": 0.16 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "xe", + "line": 329, + "self_ms": 0.15 + }, + { + "source": "runtime", + "name": "(program)", + "self_ms": 0.14 + }, + { + "source": "../../../src/utils/repo-path.ts", + "name": "u", + "line": 5, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-slot@1.2.3_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-slot/dist/index.mjs", + "name": "React.forwardRef", + "line": 32, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/parser.js", + "name": "scalar", + "line": 428, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "name": "Y", + "line": 76, + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "update", + "line": 106, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/path.js", + "name": "ds", + "line": 45, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "se", + "line": 78, + "self_ms": 0.13 + }, + { + "source": "../../../src/components/library-layout-navigation.ts", + "name": "ye", + "line": 20, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "name": "React$1.forwardRef", + "line": 314, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@phosphor-icons/react/dist/csr/ArrowLeft.es.js", + "name": "o.forwardRef", + "line": 4, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "name": "executeMiddleware", + "line": 89, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "_emitCodePoint", + "line": 456, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "name": "getFragmentParser", + "line": 119, + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/_library/$libraryId/$version.docs.$.tsx", + "name": "head", + "line": 80, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/utils.js", + "name": "ct", + "line": 257, + "self_ms": 0.13 + }, + { + "source": "../../src/utils/prod-diagnostics.server.ts", + "name": "c", + "line": 79, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "O", + "line": 413, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "Et", + "line": 115, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "name": "onEof", + "line": 1062, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "onParse", + "line": 2798, + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/__root.tsx", + "name": "nu", + "line": 290, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "name": "Ra", + "line": 267, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-context@1.1.2_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-context/dist/index.mjs", + "name": "React.useMemo", + "line": 48, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/core/attributes.js", + "name": "Ja", + "line": 7, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "oe", + "line": 37, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "ce", + "line": 134, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/index.js", + "name": "Ra", + "line": 33, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Tt", + "line": 1893, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/headContentUtils.js", + "name": "", + "line": 90, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "name": "__executeServer", + "line": 62, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "name": "_", + "line": 2, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "_appendCharToCurrentCharacterToken", + "line": 442, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-ssr-query-core@1.169.2_@tanstack+query-core@5.102.8_@tanstack+router-core@1.171.34/node_modules/@tanstack/router-ssr-query-core/dist/esm/index.js", + "name": "Zm.e.options.dehydrate", + "line": 30, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "_stateData", + "line": 792, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/headers.js", + "name": "Zp", + "line": 3, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/load-server.js", + "name": "Pu", + "line": 115, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "A", + "line": 298, + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-tooltip@1.2.8_@types+react-dom@19.2.3_@types+react@19.2.14__@types+reac_f164644780a8f95ec4733d733b3b08bc/node_modules/@radix-ui/react-tooltip/dist/index.mjs", + "name": "W", + "line": 71, + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs.functions.ts?tss-serverfn-split", + "name": "loadDocumentsServerModule", + "line": 135, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "name": "_s", + "line": 141, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/path.js", + "name": "zc", + "line": 110, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "pu", + "line": 891, + "self_ms": 0.12 + }, + { + "source": "../../../src/components/Navbar.tsx", + "name": "Ld", + "line": 341, + "self_ms": 0.12 + }, + { + "source": "../../../src/ui/Button.tsx", + "name": "R", + "line": 96, + "self_ms": 0.12 + }, + { + "source": "../../../src/components/ds/ui/index.tsx", + "name": "", + "line": 203, + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "Ae", + "line": 4, + "self_ms": 0.11 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/patterns.js", + "name": "_", + "line": 4, + "self_ms": 0.11 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "name": "s", + "line": 251, + "self_ms": 0.11 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "name": "i", + "line": 85, + "self_ms": 0.08 + }, + { + "source": "../../../src/components/Dropdown.tsx", + "name": "k", + "line": 53, + "self_ms": 0.07 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "name": "H", + "line": 201, + "self_ms": 0.06 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "mi", + "line": 57, + "self_ms": 0.06 + }, + { + "source": "../../../src/utils/markdown/live-example.ts", + "name": "g", + "line": 33, + "self_ms": 0.05 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "name": "g", + "line": 31, + "self_ms": 0.05 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/docs.js", + "name": "Y", + "line": 18, + "self_ms": 0.05 + }, + { + "source": "../../../src/utils/partner-placement.ts", + "name": "", + "line": 181, + "self_ms": 0.04 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "name": "Ht", + "line": 45, + "self_ms": 0.04 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Fr", + "line": 400, + "self_ms": 0.03 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/utils.js", + "name": "Nc", + "line": 292, + "self_ms": 0.02 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "name": "a", + "line": 304, + "self_ms": 0.02 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "name": "E", + "line": 193, + "self_ms": 0.02 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Hn", + "line": 2449, + "self_ms": 0.01 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "name": "Wr", + "line": 2478, + "self_ms": 0.01 + } + ], + "sources": [ + { + "source": "runtime", + "self_ms": 7.75 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/subscribable.js", + "self_ms": 2.89 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/walk.js", + "self_ms": 1.86 + }, + { + "source": "../../../node_modules/.pnpm/seroval@1.6.7/node_modules/seroval/dist/index.js", + "self_ms": 1.62 + }, + { + "source": "../../../src/utils/github-content-cache.server.ts", + "self_ms": 1.43 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/parser.js", + "self_ms": 0.96 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/server/stream.js", + "self_ms": 0.64 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.js", + "self_ms": 0.64 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+start-client-core@1.170.34/node_modules/@tanstack/start-client-core/dist/esm/createServerFn.js", + "self_ms": 0.51 + }, + { + "source": "../../../src/utils/markdown/processor.ts", + "self_ms": 0.45 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/htmlBoundaryScanner.js", + "self_ms": 0.39 + }, + { + "source": "../../../src/utils/docsNavTabs.ts", + "self_ms": 0.37 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/router.js", + "self_ms": 0.36 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/inline.js", + "self_ms": 0.36 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/script.js", + "self_ms": 0.34 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/link.js", + "self_ms": 0.26 + }, + { + "source": "../../src/server.ts", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/parser/index.js", + "self_ms": 0.26 + }, + { + "source": "../../../src/utils/request-timing.server.ts", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/path.js", + "self_ms": 0.26 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/react.js", + "self_ms": 0.18 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+query-core@5.102.8/node_modules/@tanstack/query-core/build/modern/queryObserver.js", + "self_ms": 0.16 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/core.js", + "self_ms": 0.15 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/utils.js", + "self_ms": 0.15 + }, + { + "source": "../../../src/utils/repo-path.ts", + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-slot@1.2.3_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-slot/dist/index.mjs", + "self_ms": 0.14 + }, + { + "source": "../../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/parser.js", + "self_ms": 0.14 + }, + { + "source": "../../../src/components/library-layout-navigation.ts", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@phosphor-icons/react/dist/csr/ArrowLeft.es.js", + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/_library/$libraryId/$version.docs.$.tsx", + "self_ms": 0.13 + }, + { + "source": "../../src/utils/prod-diagnostics.server.ts", + "self_ms": 0.13 + }, + { + "source": "../../../src/routes/__root.tsx", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-context@1.1.2_@types+react@19.2.14_react@19.2.3/node_modules/@radix-ui/react-context/dist/index.mjs", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+redact@0.1.4_vite@8.0.16_@types+node@25.5.0_esbuild@0.27.4_jiti@2.7.0_tsx@4.21.0_yaml@2.9.0_/node_modules/@tanstack/redact/dist/core/attributes.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/parse5@7.3.0/node_modules/parse5/dist/index.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+react-router@1.170.41_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@tanstack/react-router/dist/esm/headContentUtils.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-ssr-query-core@1.169.2_@tanstack+query-core@5.102.8_@tanstack+router-core@1.171.34/node_modules/@tanstack/router-ssr-query-core/dist/esm/index.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/ssr/headers.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/load-server.js", + "self_ms": 0.13 + }, + { + "source": "../../../node_modules/.pnpm/@radix-ui+react-tooltip@1.2.8_@types+react-dom@19.2.3_@types+react@19.2.14__@types+reac_f164644780a8f95ec4733d733b3b08bc/node_modules/@radix-ui/react-tooltip/dist/index.mjs", + "self_ms": 0.13 + }, + { + "source": "../../../src/utils/docs.functions.ts?tss-serverfn-split", + "self_ms": 0.12 + }, + { + "source": "../../../src/components/Navbar.tsx", + "self_ms": 0.12 + }, + { + "source": "../../../src/ui/Button.tsx", + "self_ms": 0.12 + }, + { + "source": "../../../src/components/ds/ui/index.tsx", + "self_ms": 0.12 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+highlight@1.0.0/node_modules/@tanstack/highlight/dist/internal/patterns.js", + "self_ms": 0.11 + }, + { + "source": "../../../src/components/Dropdown.tsx", + "self_ms": 0.07 + }, + { + "source": "../../../src/utils/markdown/live-example.ts", + "self_ms": 0.05 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+markdown@1.0.0_octane@0.1.13_react-dom@19.2.3_react@19.2.3__react@19.2.3_vite_be296f75eff2a49ec73fb9e97ef8d921/node_modules/@tanstack/markdown/dist/extensions/docs.js", + "self_ms": 0.05 + }, + { + "source": "../../../src/utils/partner-placement.ts", + "self_ms": 0.04 + }, + { + "source": "../../../node_modules/.pnpm/@tanstack+router-core@1.171.34/node_modules/@tanstack/router-core/dist/esm/new-process-route-tree.js", + "self_ms": 0.04 + } + ] + } + }, + "cleanup": [ + { + "worker": "tanstack-cold-probe-minimal", + "exit_code": 0, + "deleted": true + }, + { + "worker": "tanstack-cold-probe-tiny", + "exit_code": 0, + "deleted": true + }, + { + "worker": "tanstack-cold-probe-full", + "exit_code": 0, + "deleted": true + }, + { + "worker": "tanstack-cold-probe-data", + "exit_code": 0, + "deleted": true + }, + { + "worker": "tanstack-cold-probe-no-wasm", + "exit_code": 0, + "deleted": true + }, + { + "worker": "tanstack-cold-probe-single-js", + "exit_code": 0, + "deleted": true + }, + { + "worker": "tanstack-cold-probe-many-js", + "exit_code": 0, + "deleted": true + }, + { + "worker": "tanstack-cold-probe-monolith", + "exit_code": 0, + "deleted": true + } + ], + "production_version_after_experiment": "3b554871-c23c-4825-9bb4-f9bc27dc5466" +} diff --git a/docs/worker-cold-start-probes/README.md b/docs/worker-cold-start-probes/README.md new file mode 100644 index 000000000..7ec68c598 --- /dev/null +++ b/docs/worker-cold-start-probes/README.md @@ -0,0 +1,20 @@ +# Archived diagnostic templates + +These files document the experiment. They are not application code or a proposed deployment. In particular, the empty-WASM control cannot generate OG images, and the rebundle did not solve the cold delay. + +- `probe.js.txt` is the standalone minimal handler. `/noop` performs no I/O. `/cache` performs a Cache API miss, and `/r2` performs a read-only HEAD miss. +- `measure.py.txt` records curl timing, response instance/counter headers, and ray IDs. Its optional arguments select a path and request count. The hostnames in the file refer to temporary Workers that have been deleted. +- `rebundle.mjs.txt` records the esbuild experiment. It expects the original full-probe files/configuration prepared in `dist/server`; it is not a standalone project build command. + +The complete Worker upload was tested by placing the minimal handler beside the site's built `index.js` and `assets` modules, with the same ESModule/CompiledWasm discovery rules. Merely sharing that upload was enough to reproduce the delay, without importing the application. The full-app variant added a static import of built `index.js` and forwarded `/docs` to the actual docs URL. + +The synthetic variants used the same minimal handler and binding configuration: + +1. Data: concatenate built `index.js` and the server asset JavaScript into one file, uploaded with a Data rule. +2. Single JS: encode the same text as `export default ` in one extra ESModule. +3. Many JS: split that text into 1,000 slices, each exported as a JSON string in an ESModule. +4. Empty WASM: retain the site's JavaScript modules, replace its original compiled WASM with the eight-byte empty WASM module, and only exercise `/noop`. + +Each config used the same account, compatibility date/flags, and read-only diagnostic paths. Only `GITHUB_CONTENT_CACHE` was bound, with no database, application secrets, Durable Object namespaces, or scheduled tasks. Full-app and rebundled probes also retained the production static assets binding. All probes enabled native tracing and logs. Production routes were never attached. + +Join request records to Wrangler tail output by `cf-ray`. Exclude non-200 responses from latency summaries while retaining them in the raw dataset. Separate first observed instance requests from reused instances. Report connection setup independently, and do not sum application spans that overlap or depend on Worker I/O clocks. diff --git a/docs/worker-cold-start-probes/measure.py.txt b/docs/worker-cold-start-probes/measure.py.txt new file mode 100644 index 000000000..787524803 --- /dev/null +++ b/docs/worker-cold-start-probes/measure.py.txt @@ -0,0 +1,16 @@ +import subprocess,json,sys,time +from pathlib import Path +name=sys.argv[1];run=sys.argv[2] if len(sys.argv)>2 else 'initial' +rows=[] +paths=['/noop']*8+['/cache']*3+['/r2']*3 +if name not in ['tiny','minimal']:paths+=['/docs']*5+['/noop']*2 +if len(sys.argv)>3: paths=[sys.argv[3]]*int(sys.argv[4]) +for i,path in enumerate(paths): + out=Path(f'/tmp/docs-cold-{name}-{run}-{i}.headers') + p=subprocess.run(['curl','-sS','--max-time','30','-H','Accept: text/html','-D',str(out),'-o',f'/tmp/docs-cold-{name}-{run}-{i}.body','-w','%{json}',f'https://tanstack-cold-probe-{name}.thetanstack.workers.dev{path}'],capture_output=True,text=True) + if p.returncode:print('curl error',p.stderr,flush=True);continue + m=json.loads(p.stdout);h={} + for line in out.read_text().splitlines(): + if ':' in line:k,v=line.split(':',1);h[k.lower()]=v.strip() + row={'worker':name,'run':run,'path':path,'status':m['http_code'],'ttfb_ms':round(m['time_starttransfer']*1000,1),'total_ms':round(m['time_total']*1000,1),'tls_ms':round(m['time_appconnect']*1000,1),'after_connection_ms':round((m['time_starttransfer']-m['time_pretransfer'])*1000,1),'ray':h.get('cf-ray'),'instance':h.get('x-probe-instance'),'instance_request':h.get('x-probe-request'),'phase_ms':h.get('x-probe-phase-ms'),'server_timing':h.get('server-timing')} + rows.append(row);print(json.dumps(row),flush=True);Path(f'/tmp/docs-cold-{name}-{run}-measurements.json').write_text(json.dumps(rows,indent=2)+'\n');time.sleep(.5) diff --git a/docs/worker-cold-start-probes/probe.js.txt b/docs/worker-cold-start-probes/probe.js.txt new file mode 100644 index 000000000..22587630d --- /dev/null +++ b/docs/worker-cold-start-probes/probe.js.txt @@ -0,0 +1,34 @@ +let instance +let requests = 0 +export default { + async fetch(request, env, ctx) { + instance ??= crypto.randomUUID() + const count = ++requests + const started = performance.now() + const path = new URL(request.url).pathname + let response + let phaseMs = 0 + if (path === '/cache') { + const t = performance.now() + await caches.default.match('https://blob-storage-cache.tanstack.internal/diagnostic-missing-key') + phaseMs = performance.now() - t + response = new Response('cache read complete') + } else if (path === '/r2') { + const t = performance.now() + await env.GITHUB_CONTENT_CACHE.head('diagnostic-missing-key') + phaseMs = performance.now() - t + response = new Response('r2 read complete') + } else if (path === '/docs') { + response = new Response('not supported', {status:404}) + } else { + response = new Response('ok') + } + const headers = new Headers(response.headers) + headers.set('Cache-Control', 'private, no-store') + headers.set('X-Probe-Instance', instance) + headers.set('X-Probe-Request', String(count)) + headers.set('X-Probe-Phase-Ms', String(phaseMs)) + console.log(JSON.stringify({logType:'docs_cold_probe',instance,count,path,ray:request.headers.get('cf-ray'),phaseMs,headersMs:performance.now()-started})) + return new Response(response.body, {status:response.status,headers}) + } +} diff --git a/docs/worker-cold-start-probes/rebundle.mjs.txt b/docs/worker-cold-start-probes/rebundle.mjs.txt new file mode 100644 index 000000000..f0fbe265e --- /dev/null +++ b/docs/worker-cold-start-probes/rebundle.mjs.txt @@ -0,0 +1,11 @@ +import { createRequire } from 'node:module' +import {mkdir,readFile,writeFile,copyFile} from 'node:fs/promises' +import path from 'node:path' +const require=createRequire(process.cwd()+'/package.json') +const {build}=require('esbuild') +const out='/tmp/docs-cold-monolith' +await mkdir(out,{recursive:true}) +const result=await build({entryPoints:[path.resolve('dist/server/index.js')],outfile:out+'/app.js',bundle:true,format:'esm',platform:'node',ignoreAnnotations:true,treeShaking:false,logLevel:'silent',target:'es2022',minify:true,metafile:true,external:['cloudflare:workers','node:*'],plugins:[{name:'preserve-cloudflare-wasm',setup(build){build.onResolve({filter:/\.wasm$/},async args=>{const source=path.resolve(args.resolveDir,args.path);const basename=path.basename(source);await copyFile(source,out+'/'+basename);return {path:'./'+basename,external:true}})}}]}) +await writeFile(out+'/metafile.json',JSON.stringify(result.metafile)) +let probe=await readFile('dist/server/cold-probe-full.js','utf8');probe=probe.replace("import app from './index.js'","import app from './app.js'");await writeFile(out+'/index.js',probe) +const cfg=JSON.parse(await readFile('dist/server/cold-probe-full.wrangler.json','utf8'));cfg.name='tanstack-cold-probe-monolith';cfg.main='index.js';cfg.find_additional_modules=true;cfg.assets.directory=path.resolve('dist/client');await writeFile(out+'/wrangler.json',JSON.stringify(cfg));console.log(JSON.stringify({outputs:Object.entries(result.metafile.outputs).map(([name,value])=>({name,bytes:value.bytes})),inputModules:Object.keys(result.metafile.inputs).length})) diff --git a/docs/worker-deployment-boundaries.md b/docs/worker-deployment-boundaries.md new file mode 100644 index 000000000..7abeac91e --- /dev/null +++ b/docs/worker-deployment-boundaries.md @@ -0,0 +1,84 @@ +# Worker deployment boundaries + +The browser still uses one TanStack Start app and its existing route tree. One +Vite build produces four independent Cloudflare Worker uploads: + +| Output | Worker | Owns | +| -------------- | ------------------ | ------------------------------------------------------------------------------------- | +| `dist/server` | `tanstack-com` | Pages, docs, assets, auth, Start server functions, site cron tasks | +| `dist/og` | `tanstack-og` | OG and README image generation, including Takumi WASM | +| `dist/chat` | `tanstack-chat` | Chat HTTP handlers, model execution, Durable Objects, chat workflow and sync recovery | +| `dist/starter` | `tanstack-starter` | Project generation, embedded templates, starter API and GitHub deployment | + +Public URLs stay the same. The Start API routes forward the original Request +through service bindings, preserving cookies, origin, query parameters, bodies +and streamed responses. Each backend retains its existing authentication, +authorization, same-origin and rate-limit checks. Auxiliary Workers have no +public routes or workers.dev endpoints. + +Implementation files stay with their features. Thin backend HTTP adapters live +under `src/chat/server/http` and `src/application-starter/api/http`. Their route +tables put specific paths before parameters and splats. `worker-boundaries.test.ts` +checks that their paths and methods match the public Start API routes. + +The chat app, shared project workbenches, charts builder and icon browser use +Start's `createClientOnlyFn` around their lazy component imports. `ClientOnly` +alone does not remove a dependency from the server upload. + +The production build checks both eager chat imports and every uploaded site +module, including lazy chunks. It fails if the extracted heavy implementations +return to the site upload. Six small provider templates remain with the site's +existing example deployment API. + +## Development + +`pnpm dev:cloudflare` starts the app and its auxiliary Workers together, using +local service bindings. The existing root `.env.local` is loaded by `with-env`. +The child configs declare their required secrets so Wrangler also reads them +from the inherited process environment. Hyperdrive is disabled in development, +as in the existing site config, and the local database URL is inherited from +`with-env`. + +For a production build preview with local credentials, use: + +```sh +pnpm build:cloudflare +pnpm with-env vite preview +``` + +## Deployment + +```sh +pnpm build:cloudflare +pnpm deploy:cloudflare:workers +``` + +The deployment command publishes OG, starter, chat, then the site. It preserves +existing remote variables and secrets. It does not run database migrations or +purge the CDN. The existing broader deployment commands still perform their +existing database preparation and cache purge steps. + +Provision the child Workers' declared secrets before routing production +requests to them. Never put secret values in Wrangler configs or Git. + +The first rollout prepares the chat Worker's three SQLite namespaces with +`expecting-transfer` declarations and bindings to the existing source namespaces. +The site then commits the transfers with `transferred` declarations. The chat +Worker activates its own bindings and takes ownership of the existing workflow. +Finally, the site removes the completed transfer declarations. The deployment +script detects this first rollout through the existing workflow's owner and +performs the stages in order. It checks required child secrets before starting. + +Subsequent deployments publish the chat Worker before the site. The chat config +uses explicit SQLite exports, so local development also gets SQLite storage. +Never use `new_sqlite_classes` or create fresh production namespaces for the +existing conversations. + +The chat Worker owns workspace-sync recovery; the site keeps its other cron +work. The site no longer exports chat classes or owns Cloudflare workflow +bindings. A disposable Cloudflare test verifies namespace and workflow +continuity through this same rollout, including requests between stages. + +A rollback after transferring namespaces must retain the new namespace owner +and cross-script bindings, or explicitly transfer those namespaces back. Simply +deploying the old monolithic config is not a complete rollback plan. diff --git a/docs/worker-split-measurements.json b/docs/worker-split-measurements.json new file mode 100644 index 000000000..0ee4c8244 --- /dev/null +++ b/docs/worker-split-measurements.json @@ -0,0 +1,1714 @@ +{ + "measured_at_utc": "2026-10-15", + "method": "Interleaved requests, separate curl connection per request, alternating order, 250ms gap. Identical minimal handler with original versus reduced site modules uploaded but never imported. No credentials, database, storage or service bindings. First observed instance is identified by X-Probe-Request: 1, it does not prove every Cloudflare platform cache was cold. After-connection time subtracts time_pretransfer from time_starttransfer and still includes network round-trip time. These measurements isolate Worker payload preparation, they are not production documentation TTFB measurements.", + "summary": { + "before": { + "requests": 40, + "successes": 40, + "first_observed_instance_requests": 12, + "first_observed_instance_median_after_connection_ms": 1048.05, + "reused_instance_requests": 28, + "reused_instance_median_after_connection_ms": 67.65 + }, + "after": { + "requests": 40, + "successes": 40, + "first_observed_instance_requests": 13, + "first_observed_instance_median_after_connection_ms": 471.6, + "reused_instance_requests": 27, + "reused_instance_median_after_connection_ms": 65.2 + } + }, + "probes": { + "before": { + "worker": "tanstack-split-payload-before", + "version": "e0ac12ea-a3a0-49db-9d71-d7aa59319892", + "upload_kib": 26260.77, + "gzip_kib": 8459.71 + }, + "after": { + "worker": "tanstack-split-payload-after", + "version": "65404bd3-081c-46ef-b0e6-195d8ef8f2f1", + "upload_kib": 10935.11, + "gzip_kib": 3158.83 + } + }, + "measurements": [ + { + "variant": "before", + "index": 0, + "status": 200, + "ttfb_ms": 1419.0, + "after_connection_ms": 1293.6, + "instance": "8f5ee7bc-c49e-4605-b581-f82a061261f7", + "instance_request": "1", + "ray": "a4b27b2038604b91-DFW" + }, + { + "variant": "after", + "index": 0, + "status": 200, + "ttfb_ms": 977.1, + "after_connection_ms": 849.1, + "instance": "d57c1d58-33e2-41de-a518-556e5fae8243", + "instance_request": "1", + "ray": "a4b27b2ab96b143d-DFW" + }, + { + "variant": "after", + "index": 1, + "status": 200, + "ttfb_ms": 461.8, + "after_connection_ms": 382.1, + "instance": "8718ccd8-496d-45a1-99e4-8eac1cdb135a", + "instance_request": "1", + "ray": "a4b27b32386f4918-DFW" + }, + { + "variant": "before", + "index": 1, + "status": 200, + "ttfb_ms": 1001.5, + "after_connection_ms": 923.7, + "instance": "05101b85-77bf-4c50-b689-ac993edd792d", + "instance_request": "1", + "ray": "a4b27b36bd742c1e-DFW" + }, + { + "variant": "before", + "index": 2, + "status": 200, + "ttfb_ms": 829.6, + "after_connection_ms": 750.3, + "instance": "32d16e77-1d91-44cb-97c5-5df22e6e9578", + "instance_request": "1", + "ray": "a4b27b3eacc269dc-DFW" + }, + { + "variant": "after", + "index": 2, + "status": 200, + "ttfb_ms": 812.6, + "after_connection_ms": 727.4, + "instance": "5f4301a6-1e2b-4020-894b-cf66c982f69c", + "instance_request": "1", + "ray": "a4b27b459ee947a5-DFW" + }, + { + "variant": "after", + "index": 3, + "status": 200, + "ttfb_ms": 484.8, + "after_connection_ms": 403.9, + "instance": "943b718d-2f8c-43b4-9c16-1235c0f2d4c1", + "instance_request": "1", + "ray": "a4b27b4c5ec611de-DFW" + }, + { + "variant": "before", + "index": 3, + "status": 200, + "ttfb_ms": 2800.6, + "after_connection_ms": 2714.3, + "instance": "9c5ded07-e4d6-4de2-b8b7-e65b114f30cc", + "instance_request": "1", + "ray": "a4b27b511fbceac9-DFW" + }, + { + "variant": "before", + "index": 4, + "status": 200, + "ttfb_ms": 139.1, + "after_connection_ms": 58.7, + "instance": "05101b85-77bf-4c50-b689-ac993edd792d", + "instance_request": "2", + "ray": "a4b27b643a89159f-DFW" + }, + { + "variant": "after", + "index": 4, + "status": 200, + "ttfb_ms": 549.5, + "after_connection_ms": 471.6, + "instance": "49eeb249-7c5f-48e9-9530-d99a10bbfff9", + "instance_request": "1", + "ray": "a4b27b66c9c993ac-DFW" + }, + { + "variant": "after", + "index": 5, + "status": 200, + "ttfb_ms": 514.4, + "after_connection_ms": 436.8, + "instance": "124075ac-8a23-4729-af2f-1e0263b93caf", + "instance_request": "1", + "ray": "a4b27b6bd8056781-DFW" + }, + { + "variant": "before", + "index": 5, + "status": 200, + "ttfb_ms": 150.9, + "after_connection_ms": 71.3, + "instance": "32d16e77-1d91-44cb-97c5-5df22e6e9578", + "instance_request": "2", + "ray": "a4b27b70bf7666ef-DFW" + }, + { + "variant": "before", + "index": 6, + "status": 200, + "ttfb_ms": 139.8, + "after_connection_ms": 61.1, + "instance": "8f5ee7bc-c49e-4605-b581-f82a061261f7", + "instance_request": "2", + "ray": "a4b27b7349044aa7-DFW" + }, + { + "variant": "after", + "index": 6, + "status": 200, + "ttfb_ms": 136.9, + "after_connection_ms": 60.4, + "instance": "8718ccd8-496d-45a1-99e4-8eac1cdb135a", + "instance_request": "2", + "ray": "a4b27b75cf5aebf6-DFW" + }, + { + "variant": "after", + "index": 7, + "status": 200, + "ttfb_ms": 140.6, + "after_connection_ms": 62.0, + "instance": "49eeb249-7c5f-48e9-9530-d99a10bbfff9", + "instance_request": "2", + "ray": "a4b27b784f69a915-DFW" + }, + { + "variant": "before", + "index": 7, + "status": 200, + "ttfb_ms": 152.1, + "after_connection_ms": 67.6, + "instance": "05101b85-77bf-4c50-b689-ac993edd792d", + "instance_request": "3", + "ray": "a4b27b7ade53b1ee-DFW" + }, + { + "variant": "before", + "index": 8, + "status": 200, + "ttfb_ms": 1356.4, + "after_connection_ms": 1274.9, + "instance": "e44ed419-558d-408a-910a-50715e5fd4f7", + "instance_request": "1", + "ray": "a4b27b7d6c7a6b22-DFW" + }, + { + "variant": "after", + "index": 8, + "status": 200, + "ttfb_ms": 191.5, + "after_connection_ms": 97.9, + "instance": "943b718d-2f8c-43b4-9c16-1235c0f2d4c1", + "instance_request": "2", + "ray": "a4b27b87aea1e76e-DFW" + }, + { + "variant": "after", + "index": 9, + "status": 200, + "ttfb_ms": 836.2, + "after_connection_ms": 701.5, + "instance": "b5ea49ab-6728-4bae-93e7-2812be869350", + "instance_request": "1", + "ray": "a4b27b8abbb16bb9-DFW" + }, + { + "variant": "before", + "index": 9, + "status": 200, + "ttfb_ms": 786.7, + "after_connection_ms": 708.6, + "instance": "79fb3f8f-7b0c-48ed-b62c-f36e1a851526", + "instance_request": "1", + "ray": "a4b27b913bdda619-DFW" + }, + { + "variant": "before", + "index": 10, + "status": 200, + "ttfb_ms": 157.8, + "after_connection_ms": 76.1, + "instance": "79fb3f8f-7b0c-48ed-b62c-f36e1a851526", + "instance_request": "2", + "ray": "a4b27b97d8bc5618-DFW" + }, + { + "variant": "after", + "index": 10, + "status": 200, + "ttfb_ms": 514.2, + "after_connection_ms": 430.9, + "instance": "0379d764-7ec6-4e2e-bc20-f6e9ab8f8a55", + "instance_request": "1", + "ray": "a4b27b9a7d2de5de-DFW" + }, + { + "variant": "after", + "index": 11, + "status": 200, + "ttfb_ms": 124.9, + "after_connection_ms": 45.1, + "instance": "49eeb249-7c5f-48e9-9530-d99a10bbfff9", + "instance_request": "3", + "ray": "a4b27b9f4b32a922-DFW" + }, + { + "variant": "before", + "index": 11, + "status": 200, + "ttfb_ms": 1517.3, + "after_connection_ms": 1435.4, + "instance": "4109a8f4-0e7d-4c83-bc70-3793a8d00162", + "instance_request": "1", + "ray": "a4b27ba1c9f946e6-DFW" + }, + { + "variant": "before", + "index": 12, + "status": 200, + "ttfb_ms": 916.2, + "after_connection_ms": 833.4, + "instance": "d38c1d5d-db05-4e65-9ce7-4101daa5a52f", + "instance_request": "1", + "ray": "a4b27baceee7f05e-DFW" + }, + { + "variant": "after", + "index": 12, + "status": 200, + "ttfb_ms": 139.6, + "after_connection_ms": 60.2, + "instance": "8718ccd8-496d-45a1-99e4-8eac1cdb135a", + "instance_request": "3", + "ray": "a4b27bb439655549-DFW" + }, + { + "variant": "after", + "index": 13, + "status": 200, + "ttfb_ms": 143.4, + "after_connection_ms": 65.0, + "instance": "49eeb249-7c5f-48e9-9530-d99a10bbfff9", + "instance_request": "4", + "ray": "a4b27bb6cfea3797-DFW" + }, + { + "variant": "before", + "index": 13, + "status": 200, + "ttfb_ms": 146.1, + "after_connection_ms": 64.1, + "instance": "8f5ee7bc-c49e-4605-b581-f82a061261f7", + "instance_request": "3", + "ray": "a4b27bb95824e912-DFW" + }, + { + "variant": "before", + "index": 14, + "status": 200, + "ttfb_ms": 123.5, + "after_connection_ms": 46.4, + "instance": "32d16e77-1d91-44cb-97c5-5df22e6e9578", + "instance_request": "3", + "ray": "a4b27bbbf90066f5-DFW" + }, + { + "variant": "after", + "index": 14, + "status": 200, + "ttfb_ms": 143.5, + "after_connection_ms": 64.1, + "instance": "124075ac-8a23-4729-af2f-1e0263b93caf", + "instance_request": "2", + "ray": "a4b27bbe7f816a03-DFW" + }, + { + "variant": "after", + "index": 15, + "status": 200, + "ttfb_ms": 559.5, + "after_connection_ms": 483.0, + "instance": "55b75676-4c05-4329-af21-947c76bccaf4", + "instance_request": "1", + "ray": "a4b27bc0ff751021-DFW" + }, + { + "variant": "before", + "index": 15, + "status": 200, + "ttfb_ms": 159.2, + "after_connection_ms": 81.0, + "instance": "4109a8f4-0e7d-4c83-bc70-3793a8d00162", + "instance_request": "2", + "ray": "a4b27bc61c9a4671-DFW" + }, + { + "variant": "before", + "index": 16, + "status": 200, + "ttfb_ms": 1176.1, + "after_connection_ms": 1097.0, + "instance": "ea854113-4e85-42d2-b99b-9ad3fa2d683a", + "instance_request": "1", + "ray": "a4b27bc8cdea638a-DFW" + }, + { + "variant": "after", + "index": 16, + "status": 200, + "ttfb_ms": 455.1, + "after_connection_ms": 374.0, + "instance": "e7f9fddf-501e-4a61-b5b4-62154dc89a56", + "instance_request": "1", + "ray": "a4b27bd1c99a1db5-DFW" + }, + { + "variant": "after", + "index": 17, + "status": 200, + "ttfb_ms": 151.0, + "after_connection_ms": 68.2, + "instance": "124075ac-8a23-4729-af2f-1e0263b93caf", + "instance_request": "3", + "ray": "a4b27bd64fc769bf-DFW" + }, + { + "variant": "before", + "index": 17, + "status": 200, + "ttfb_ms": 1474.1, + "after_connection_ms": 1394.6, + "instance": "4e815e29-0e99-438c-9b56-33f345941695", + "instance_request": "1", + "ray": "a4b27bd8d99c72ff-DFW" + }, + { + "variant": "before", + "index": 18, + "status": 200, + "ttfb_ms": 143.1, + "after_connection_ms": 60.7, + "instance": "d38c1d5d-db05-4e65-9ce7-4101daa5a52f", + "instance_request": "2", + "ray": "a4b27be3c947f0b2-DFW" + }, + { + "variant": "after", + "index": 18, + "status": 200, + "ttfb_ms": 240.3, + "after_connection_ms": 62.0, + "instance": "e7f9fddf-501e-4a61-b5b4-62154dc89a56", + "instance_request": "2", + "ray": "a4b27be6e9f309ac-DFW" + }, + { + "variant": "after", + "index": 19, + "status": 200, + "ttfb_ms": 180.3, + "after_connection_ms": 95.0, + "instance": "e7f9fddf-501e-4a61-b5b4-62154dc89a56", + "instance_request": "3", + "ray": "a4b27be97c365607-DFW" + }, + { + "variant": "before", + "index": 19, + "status": 200, + "ttfb_ms": 205.5, + "after_connection_ms": 67.5, + "instance": "ea854113-4e85-42d2-b99b-9ad3fa2d683a", + "instance_request": "2", + "ray": "a4b27bec8cbe2562-DFW" + }, + { + "variant": "before", + "index": 20, + "status": 200, + "ttfb_ms": 161.7, + "after_connection_ms": 62.6, + "instance": "8f5ee7bc-c49e-4605-b581-f82a061261f7", + "instance_request": "4", + "ray": "a4b27bef5d0c168b-DFW" + }, + { + "variant": "after", + "index": 20, + "status": 200, + "ttfb_ms": 170.6, + "after_connection_ms": 87.6, + "instance": "5f4301a6-1e2b-4020-894b-cf66c982f69c", + "instance_request": "2", + "ray": "a4b27bf1ec524684-DFW" + }, + { + "variant": "after", + "index": 21, + "status": 200, + "ttfb_ms": 263.5, + "after_connection_ms": 153.3, + "instance": "5f4301a6-1e2b-4020-894b-cf66c982f69c", + "instance_request": "3", + "ray": "a4b27bf4cb794781-DFW" + }, + { + "variant": "before", + "index": 21, + "status": 200, + "ttfb_ms": 1133.5, + "after_connection_ms": 999.1, + "instance": "3ea78bbe-dc80-47f2-bbc5-35b748baa476", + "instance_request": "1", + "ray": "a4b27bf849f607b7-DFW" + }, + { + "variant": "before", + "index": 22, + "status": 200, + "ttfb_ms": 215.8, + "after_connection_ms": 78.9, + "instance": "4109a8f4-0e7d-4c83-bc70-3793a8d00162", + "instance_request": "3", + "ray": "a4b27c00fa9e4683-DFW" + }, + { + "variant": "after", + "index": 22, + "status": 200, + "ttfb_ms": 133.8, + "after_connection_ms": 55.9, + "instance": "e7f9fddf-501e-4a61-b5b4-62154dc89a56", + "instance_request": "4", + "ray": "a4b27c03aed0686c-DFW" + }, + { + "variant": "after", + "index": 23, + "status": 200, + "ttfb_ms": 966.2, + "after_connection_ms": 886.4, + "instance": "f4964a6c-7496-47e4-94ee-330ade7a557c", + "instance_request": "1", + "ray": "a4b27c062f5a3594-DFW" + }, + { + "variant": "before", + "index": 23, + "status": 200, + "ttfb_ms": 151.4, + "after_connection_ms": 71.1, + "instance": "3ea78bbe-dc80-47f2-bbc5-35b748baa476", + "instance_request": "2", + "ray": "a4b27c0ddf164fb7-DFW" + }, + { + "variant": "before", + "index": 24, + "status": 200, + "ttfb_ms": 177.4, + "after_connection_ms": 93.1, + "instance": "9c5ded07-e4d6-4de2-b8b7-e65b114f30cc", + "instance_request": "2", + "ray": "a4b27c107fcceac5-DFW" + }, + { + "variant": "after", + "index": 24, + "status": 200, + "ttfb_ms": 181.3, + "after_connection_ms": 93.3, + "instance": "b5ea49ab-6728-4bae-93e7-2812be869350", + "instance_request": "2", + "ray": "a4b27c134bf46b4b-DFW" + }, + { + "variant": "after", + "index": 25, + "status": 200, + "ttfb_ms": 125.1, + "after_connection_ms": 45.5, + "instance": "124075ac-8a23-4729-af2f-1e0263b93caf", + "instance_request": "4", + "ray": "a4b27c15f95f6fa8-DFW" + }, + { + "variant": "before", + "index": 25, + "status": 200, + "ttfb_ms": 142.7, + "after_connection_ms": 64.6, + "instance": "d38c1d5d-db05-4e65-9ce7-4101daa5a52f", + "instance_request": "3", + "ray": "a4b27c186820f05c-DFW" + }, + { + "variant": "before", + "index": 26, + "status": 200, + "ttfb_ms": 142.2, + "after_connection_ms": 62.9, + "instance": "4e815e29-0e99-438c-9b56-33f345941695", + "instance_request": "2", + "ray": "a4b27c1afb752d45-DFW" + }, + { + "variant": "after", + "index": 26, + "status": 200, + "ttfb_ms": 149.3, + "after_connection_ms": 68.9, + "instance": "55b75676-4c05-4329-af21-947c76bccaf4", + "instance_request": "2", + "ray": "a4b27c1d8a896e71-DFW" + }, + { + "variant": "after", + "index": 27, + "status": 200, + "ttfb_ms": 177.3, + "after_connection_ms": 93.3, + "instance": "b5ea49ab-6728-4bae-93e7-2812be869350", + "instance_request": "3", + "ray": "a4b27c201a936c5b-DFW" + }, + { + "variant": "before", + "index": 27, + "status": 200, + "ttfb_ms": 147.5, + "after_connection_ms": 68.6, + "instance": "4e815e29-0e99-438c-9b56-33f345941695", + "instance_request": "3", + "ray": "a4b27c22dd3687f4-DFW" + }, + { + "variant": "before", + "index": 28, + "status": 200, + "ttfb_ms": 150.1, + "after_connection_ms": 70.9, + "instance": "8f5ee7bc-c49e-4605-b581-f82a061261f7", + "instance_request": "5", + "ray": "a4b27c256ae44854-DFW" + }, + { + "variant": "after", + "index": 28, + "status": 200, + "ttfb_ms": 158.7, + "after_connection_ms": 81.0, + "instance": "49eeb249-7c5f-48e9-9530-d99a10bbfff9", + "instance_request": "5", + "ray": "a4b27c27fa1e5870-DFW" + }, + { + "variant": "after", + "index": 29, + "status": 200, + "ttfb_ms": 169.4, + "after_connection_ms": 91.7, + "instance": "5f4301a6-1e2b-4020-894b-cf66c982f69c", + "instance_request": "4", + "ray": "a4b27c2a9a17485b-DFW" + }, + { + "variant": "before", + "index": 29, + "status": 200, + "ttfb_ms": 147.1, + "after_connection_ms": 65.5, + "instance": "d38c1d5d-db05-4e65-9ce7-4101daa5a52f", + "instance_request": "4", + "ray": "a4b27c2d5d17f048-DFW" + }, + { + "variant": "before", + "index": 30, + "status": 200, + "ttfb_ms": 158.0, + "after_connection_ms": 73.6, + "instance": "8f5ee7bc-c49e-4605-b581-f82a061261f7", + "instance_request": "6", + "ray": "a4b27c2fe9c4e772-DFW" + }, + { + "variant": "after", + "index": 30, + "status": 200, + "ttfb_ms": 618.3, + "after_connection_ms": 536.5, + "instance": "d378afa9-b704-4745-a325-1442624b8f1f", + "instance_request": "1", + "ray": "a4b27c328a8cf044-DFW" + }, + { + "variant": "after", + "index": 31, + "status": 200, + "ttfb_ms": 495.9, + "after_connection_ms": 419.2, + "instance": "cebc9ba5-d1cd-4c85-b2cf-464b686ddeb3", + "instance_request": "1", + "ray": "a4b27c380dc3124f-DFW" + }, + { + "variant": "before", + "index": 31, + "status": 200, + "ttfb_ms": 153.2, + "after_connection_ms": 69.4, + "instance": "05101b85-77bf-4c50-b689-ac993edd792d", + "instance_request": "4", + "ray": "a4b27c3cec865eca-DFW" + }, + { + "variant": "before", + "index": 32, + "status": 200, + "ttfb_ms": 843.0, + "after_connection_ms": 762.5, + "instance": "3a017cfa-4662-4678-9e11-63b112069b9a", + "instance_request": "1", + "ray": "a4b27c3f79db686c-DFW" + }, + { + "variant": "after", + "index": 32, + "status": 200, + "ttfb_ms": 151.2, + "after_connection_ms": 65.2, + "instance": "d378afa9-b704-4745-a325-1442624b8f1f", + "instance_request": "2", + "ray": "a4b27c46896df065-DFW" + }, + { + "variant": "after", + "index": 33, + "status": 200, + "ttfb_ms": 146.7, + "after_connection_ms": 65.4, + "instance": "d378afa9-b704-4745-a325-1442624b8f1f", + "instance_request": "3", + "ray": "a4b27c493da1f0b4-DFW" + }, + { + "variant": "before", + "index": 33, + "status": 200, + "ttfb_ms": 146.9, + "after_connection_ms": 66.9, + "instance": "ea854113-4e85-42d2-b99b-9ad3fa2d683a", + "instance_request": "3", + "ray": "a4b27c4bcde155cb-DFW" + }, + { + "variant": "before", + "index": 34, + "status": 200, + "ttfb_ms": 171.6, + "after_connection_ms": 92.4, + "instance": "e44ed419-558d-408a-910a-50715e5fd4f7", + "instance_request": "2", + "ray": "a4b27c4e6f286b4b-DFW" + }, + { + "variant": "after", + "index": 34, + "status": 200, + "ttfb_ms": 138.9, + "after_connection_ms": 60.7, + "instance": "943b718d-2f8c-43b4-9c16-1235c0f2d4c1", + "instance_request": "3", + "ray": "a4b27c512c0d379d-DFW" + }, + { + "variant": "after", + "index": 35, + "status": 200, + "ttfb_ms": 156.1, + "after_connection_ms": 76.7, + "instance": "5f4301a6-1e2b-4020-894b-cf66c982f69c", + "instance_request": "5", + "ray": "a4b27c53abb246dd-DFW" + }, + { + "variant": "before", + "index": 35, + "status": 200, + "ttfb_ms": 203.8, + "after_connection_ms": 59.4, + "instance": "ea854113-4e85-42d2-b99b-9ad3fa2d683a", + "instance_request": "4", + "ray": "a4b27c56ce819d60-DFW" + }, + { + "variant": "before", + "index": 36, + "status": 200, + "ttfb_ms": 142.2, + "after_connection_ms": 61.0, + "instance": "4e815e29-0e99-438c-9b56-33f345941695", + "instance_request": "4", + "ray": "a4b27c595d1428e5-DFW" + }, + { + "variant": "after", + "index": 36, + "status": 200, + "ttfb_ms": 140.3, + "after_connection_ms": 64.3, + "instance": "8718ccd8-496d-45a1-99e4-8eac1cdb135a", + "instance_request": "4", + "ray": "a4b27c5befa2c669-DFW" + }, + { + "variant": "after", + "index": 37, + "status": 200, + "ttfb_ms": 129.8, + "after_connection_ms": 45.0, + "instance": "124075ac-8a23-4729-af2f-1e0263b93caf", + "instance_request": "5", + "ray": "a4b27c5e8eb663bc-DFW" + }, + { + "variant": "before", + "index": 37, + "status": 200, + "ttfb_ms": 161.4, + "after_connection_ms": 82.4, + "instance": "e44ed419-558d-408a-910a-50715e5fd4f7", + "instance_request": "3", + "ray": "a4b27c60fbc16b57-DFW" + }, + { + "variant": "before", + "index": 38, + "status": 200, + "ttfb_ms": 145.7, + "after_connection_ms": 67.7, + "instance": "d38c1d5d-db05-4e65-9ce7-4101daa5a52f", + "instance_request": "5", + "ray": "a4b27c63aa66f081-DFW" + }, + { + "variant": "after", + "index": 38, + "status": 200, + "ttfb_ms": 159.6, + "after_connection_ms": 78.1, + "instance": "943b718d-2f8c-43b4-9c16-1235c0f2d4c1", + "instance_request": "4", + "ray": "a4b27c664aa6d47c-DFW" + }, + { + "variant": "after", + "index": 39, + "status": 200, + "ttfb_ms": 143.3, + "after_connection_ms": 61.0, + "instance": "943b718d-2f8c-43b4-9c16-1235c0f2d4c1", + "instance_request": "5", + "ray": "a4b27c690df22ca9-DFW" + }, + { + "variant": "before", + "index": 39, + "status": 200, + "ttfb_ms": 157.3, + "after_connection_ms": 77.4, + "instance": "4e815e29-0e99-438c-9b56-33f345941695", + "instance_request": "5", + "ray": "a4b27c6b999646a5-DFW" + } + ], + "transfer_validation": { + "stored_marker": "preserved", + "workflow_created_between_transfer_stages": { + "id": "during-transfer", + "status": { + "status": "complete", + "error": null, + "output": { + "marker": "preserved" + }, + "rollback": null + } + }, + "final_marker": { + "marker": "preserved" + }, + "source_cleanup_deployment_succeeded": true + }, + "cleanup": { + "workers": { + "tanstack-split-payload-before": true, + "tanstack-split-payload-after": true, + "tanstack-split-check-source": true, + "tanstack-split-check-target": true + }, + "synthetic_workflow_deleted": true + }, + "production_split_deployed": true, + "validation": { + "production_build": "passed", + "typescript": "passed", + "lint": "passed", + "unit": { + "passed": 568, + "skipped": 4 + }, + "chat": { + "files_passed": 235, + "tests_passed": 2270 + }, + "desktop": { + "passed": 20, + "skipped": 1 + }, + "browser_page_errors": 0, + "local_sqlite_namespaces": 3 + }, + "production": { + "before_version": "c59a8959-bb8f-46a7-953a-a7c600b1e350", + "after_version": "57c7a11c-699b-4116-9cfa-2d3a9ceb9736", + "workers": { + "site": "57c7a11c-699b-4116-9cfa-2d3a9ceb9736", + "chat": "0da0e4c9-bafc-4bfd-848d-b9cf13fdd64d", + "og": "4e3550be-77f7-4357-b688-8f298e7f0495", + "starter": "6c373cf8-d6b5-49f4-9434-77e71a32af51" + }, + "namespace_ids_preserved": true, + "workflow_id_preserved": true, + "state_before": { + "namespaces": [ + { + "id": "4747da39bdcb4fd291816ecfdfb6e553", + "name": "tanstack-com_WorkspaceSync", + "script": "tanstack-com", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "e2306989d3e1432c84f6497e83c368c9", + "name": "tanstack-com_StreamObject", + "script": "tanstack-com", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "05995a182725440cbcf3343d11ba39fa", + "name": "tanstack-com_Conversation", + "script": "tanstack-com", + "class": "Conversation", + "use_sqlite": true + } + ], + "workflow": { + "id": "5b66083d-acab-4b3d-9543-72bd1ed99db3", + "name": "tanstack-chat-workflows", + "script_name": "tanstack-com", + "class_name": "TanChatWorkflow" + } + }, + "state_after": { + "namespaces": [ + { + "id": "4747da39bdcb4fd291816ecfdfb6e553", + "name": "tanstack-chat_WorkspaceSync", + "script": "tanstack-chat", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "05995a182725440cbcf3343d11ba39fa", + "name": "tanstack-chat_Conversation", + "script": "tanstack-chat", + "class": "Conversation", + "use_sqlite": true + }, + { + "id": "e2306989d3e1432c84f6497e83c368c9", + "name": "tanstack-chat_StreamObject", + "script": "tanstack-chat", + "class": "StreamObject", + "use_sqlite": true + } + ], + "workflow": { + "id": "5b66083d-acab-4b3d-9543-72bd1ed99db3", + "name": "tanstack-chat-workflows", + "script_name": "tanstack-chat", + "class_name": "TanChatWorkflow" + } + }, + "summary": { + "before": { + "requests": 24, + "successes": 24, + "median_ttfb_ms": 245.9, + "p95_ttfb_ms_nearest_rank": 1120.2, + "maximum_ttfb_ms": 1969.7, + "median_after_connection_ms": 163.0, + "median_cf_worker_ms": 91.0, + "responses_over_1s": 2 + }, + "after": { + "requests": 24, + "successes": 24, + "median_ttfb_ms": 305.5, + "p95_ttfb_ms_nearest_rank": 1293.4, + "maximum_ttfb_ms": 1511.8, + "median_after_connection_ms": 185.85, + "median_cf_worker_ms": 100.0, + "responses_over_1s": 5 + }, + "settled": { + "requests": 24, + "successes": 24, + "median_ttfb_ms": 248.5, + "p95_ttfb_ms_nearest_rank": 1352.0, + "maximum_ttfb_ms": 1354.3, + "median_after_connection_ms": 168.2, + "median_cf_worker_ms": 84.0, + "responses_over_1s": 2 + } + }, + "measurements": { + "before": [ + { + "index": 0, + "status": 200, + "ttfb_ms": 1120.2, + "after_connection_ms": 1038.8, + "server_timing": "cfEdge;dur=51,cfOrigin;dur=0,cfWorker;dur=940", + "ray": "a4b28f301fb56b25-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 1, + "status": 200, + "ttfb_ms": 273.4, + "after_connection_ms": 187.5, + "server_timing": "cfEdge;dur=50,cfOrigin;dur=0,cfWorker;dur=92", + "ray": "a4b28f39af8d6b27-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 2, + "status": 200, + "ttfb_ms": 290.9, + "after_connection_ms": 211.2, + "server_timing": "cfEdge;dur=41,cfOrigin;dur=0,cfWorker;dur=125", + "ray": "a4b28f3dcfea6b7f-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 3, + "status": 200, + "ttfb_ms": 1969.7, + "after_connection_ms": 1887.8, + "server_timing": "cfEdge;dur=1287,cfOrigin;dur=0,cfWorker;dur=558", + "ray": "a4b28f422acc5062-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 4, + "status": 200, + "ttfb_ms": 339.2, + "after_connection_ms": 186.8, + "server_timing": "cfEdge;dur=39,cfOrigin;dur=0,cfWorker;dur=102", + "ray": "a4b28f516bbf90ae-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 5, + "status": 200, + "ttfb_ms": 228.8, + "after_connection_ms": 130.8, + "server_timing": "cfEdge;dur=28,cfOrigin;dur=0,cfWorker;dur=61", + "ray": "a4b28f55e98886ee-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 6, + "status": 200, + "ttfb_ms": 201.6, + "after_connection_ms": 114.4, + "server_timing": "cfEdge;dur=12,cfOrigin;dur=0,cfWorker;dur=59", + "ray": "a4b28f59df7fee0c-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 7, + "status": 200, + "ttfb_ms": 304.6, + "after_connection_ms": 224.0, + "server_timing": "cfEdge;dur=28,cfOrigin;dur=0,cfWorker;dur=154", + "ray": "a4b28f5d88aaceb7-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 8, + "status": 200, + "ttfb_ms": 273.4, + "after_connection_ms": 192.3, + "server_timing": "cfEdge;dur=47,cfOrigin;dur=0,cfWorker;dur=101", + "ray": "a4b28f620b706ba1-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 9, + "status": 200, + "ttfb_ms": 224.3, + "after_connection_ms": 143.3, + "server_timing": "cfEdge;dur=21,cfOrigin;dur=0,cfWorker;dur=77", + "ray": "a4b28f664949286c-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 10, + "status": 200, + "ttfb_ms": 238.6, + "after_connection_ms": 160.0, + "server_timing": "cfEdge;dur=28,cfOrigin;dur=0,cfWorker;dur=90", + "ray": "a4b28f6a3b4d259a-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 11, + "status": 200, + "ttfb_ms": 241.5, + "after_connection_ms": 156.8, + "server_timing": "cfEdge;dur=31,cfOrigin;dur=0,cfWorker;dur=82", + "ray": "a4b28f6e3febf1be-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 12, + "status": 200, + "ttfb_ms": 269.3, + "after_connection_ms": 187.8, + "server_timing": "cfEdge;dur=34,cfOrigin;dur=0,cfWorker;dur=109", + "ray": "a4b28f722e976c33-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 13, + "status": 200, + "ttfb_ms": 301.6, + "after_connection_ms": 196.3, + "server_timing": "cfEdge;dur=29,cfOrigin;dur=0,cfWorker;dur=82", + "ray": "a4b28f7678210af8-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 14, + "status": 200, + "ttfb_ms": 247.8, + "after_connection_ms": 162.5, + "server_timing": "cfEdge;dur=24,cfOrigin;dur=0,cfWorker;dur=92", + "ray": "a4b28f7ac8ea52a8-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 15, + "status": 200, + "ttfb_ms": 239.0, + "after_connection_ms": 156.0, + "server_timing": "cfEdge;dur=27,cfOrigin;dur=0,cfWorker;dur=87", + "ray": "a4b28f7eda970ce7-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 16, + "status": 200, + "ttfb_ms": 213.9, + "after_connection_ms": 130.9, + "server_timing": "cfEdge;dur=24,cfOrigin;dur=0,cfWorker;dur=64", + "ray": "a4b28f82de7c0c5e-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 17, + "status": 200, + "ttfb_ms": 306.3, + "after_connection_ms": 226.8, + "server_timing": "cfEdge;dur=14,cfOrigin;dur=0,cfWorker;dur=170", + "ray": "a4b28f86acf2f0a1-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 18, + "status": 200, + "ttfb_ms": 316.2, + "after_connection_ms": 234.9, + "server_timing": "cfEdge;dur=27,cfOrigin;dur=0,cfWorker;dur=166", + "ray": "a4b28f8b3832525d-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 19, + "status": 200, + "ttfb_ms": 226.1, + "after_connection_ms": 145.6, + "server_timing": "cfEdge;dur=25,cfOrigin;dur=0,cfWorker;dur=78", + "ray": "a4b28f8fafb6d132-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 20, + "status": 200, + "ttfb_ms": 202.7, + "after_connection_ms": 121.2, + "server_timing": "cfEdge;dur=19,cfOrigin;dur=0,cfWorker;dur=56", + "ray": "a4b28f93adbbf2f6-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 21, + "status": 200, + "ttfb_ms": 244.0, + "after_connection_ms": 163.5, + "server_timing": "cfEdge;dur=26,cfOrigin;dur=0,cfWorker;dur=96", + "ray": "a4b28f976d65cb75-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 22, + "status": 200, + "ttfb_ms": 239.0, + "after_connection_ms": 156.3, + "server_timing": "cfEdge;dur=39,cfOrigin;dur=0,cfWorker;dur=76", + "ray": "a4b28f9b8acfc0fe-DFW", + "cache_control": "public, max-age=0, must-revalidate" + }, + { + "index": 23, + "status": 200, + "ttfb_ms": 221.3, + "after_connection_ms": 143.7, + "server_timing": "cfEdge;dur=35,cfOrigin;dur=0,cfWorker;dur=67", + "ray": "a4b28f9f8b5df0a1-DFW", + "cache_control": "public, max-age=0, must-revalidate" + } + ], + "after": [ + { + "index": 0, + "status": 200, + "ttfb_ms": 1293.4, + "after_connection_ms": 1213.9, + "server_timing": "cfEdge;dur=722,cfOrigin;dur=0,cfWorker;dur=447", + "ray": "a4b29199cb838788-DFW", + "cache_control": "private, no-store" + }, + { + "index": 1, + "status": 200, + "ttfb_ms": 330.3, + "after_connection_ms": 247.5, + "server_timing": "cfEdge;dur=49,cfOrigin;dur=0,cfWorker;dur=156", + "ray": "a4b291a46d98b81a-DFW", + "cache_control": "private, no-store" + }, + { + "index": 2, + "status": 200, + "ttfb_ms": 904.8, + "after_connection_ms": 824.1, + "server_timing": "cfEdge;dur=432,cfOrigin;dur=0,cfWorker;dur=351", + "ray": "a4b291a90ce36a7e-DFW", + "cache_control": "private, no-store" + }, + { + "index": 3, + "status": 200, + "ttfb_ms": 836.6, + "after_connection_ms": 753.6, + "server_timing": "cfEdge;dur=445,cfOrigin;dur=0,cfWorker;dur=267", + "ray": "a4b291b14f2931a6-DFW", + "cache_control": "private, no-store" + }, + { + "index": 4, + "status": 200, + "ttfb_ms": 882.3, + "after_connection_ms": 803.4, + "server_timing": "cfEdge;dur=443,cfOrigin;dur=0,cfWorker;dur=319", + "ray": "a4b291b90cf9c86f-DFW", + "cache_control": "private, no-store" + }, + { + "index": 5, + "status": 200, + "ttfb_ms": 266.2, + "after_connection_ms": 184.2, + "server_timing": "cfEdge;dur=35,cfOrigin;dur=0,cfWorker;dur=100", + "ray": "a4b291c0f8f06bc6-DFW", + "cache_control": "private, no-store" + }, + { + "index": 6, + "status": 200, + "ttfb_ms": 234.0, + "after_connection_ms": 150.9, + "server_timing": "cfEdge;dur=47,cfOrigin;dur=0,cfWorker;dur=61", + "ray": "a4b291c539180af8-DFW", + "cache_control": "private, no-store" + }, + { + "index": 7, + "status": 200, + "ttfb_ms": 195.2, + "after_connection_ms": 117.8, + "server_timing": "cfEdge;dur=16,cfOrigin;dur=0,cfWorker;dur=58", + "ray": "a4b291c92987f432-DFW", + "cache_control": "private, no-store" + }, + { + "index": 8, + "status": 200, + "ttfb_ms": 196.9, + "after_connection_ms": 119.2, + "server_timing": "cfEdge;dur=26,cfOrigin;dur=0,cfWorker;dur=48", + "ray": "a4b291cceaca6e88-DFW", + "cache_control": "private, no-store" + }, + { + "index": 9, + "status": 200, + "ttfb_ms": 1038.2, + "after_connection_ms": 955.7, + "server_timing": "cfEdge;dur=506,cfOrigin;dur=0,cfWorker;dur=406", + "ray": "a4b291d0aab41924-DFW", + "cache_control": "private, no-store" + }, + { + "index": 10, + "status": 200, + "ttfb_ms": 245.6, + "after_connection_ms": 147.8, + "server_timing": "cfEdge;dur=25,cfOrigin;dur=0,cfWorker;dur=69", + "ray": "a4b291da8f325870-DFW", + "cache_control": "private, no-store" + }, + { + "index": 11, + "status": 200, + "ttfb_ms": 1067.7, + "after_connection_ms": 979.1, + "server_timing": "cfEdge;dur=626,cfOrigin;dur=0,cfWorker;dur=310", + "ray": "a4b291de7c14c0e5-DFW", + "cache_control": "private, no-store" + }, + { + "index": 12, + "status": 200, + "ttfb_ms": 1511.8, + "after_connection_ms": 1428.3, + "server_timing": "cfEdge;dur=881,cfOrigin;dur=0,cfWorker;dur=502", + "ray": "a4b291e79e777d5b-DFW", + "cache_control": "private, no-store" + }, + { + "index": 13, + "status": 200, + "ttfb_ms": 199.5, + "after_connection_ms": 116.9, + "server_timing": "cfEdge;dur=23,cfOrigin;dur=0,cfWorker;dur=51", + "ray": "a4b291f3992a14ef-DFW", + "cache_control": "private, no-store" + }, + { + "index": 14, + "status": 200, + "ttfb_ms": 934.8, + "after_connection_ms": 851.3, + "server_timing": "cfEdge;dur=484,cfOrigin;dur=0,cfWorker;dur=325", + "ray": "a4b291f75af87ca9-DFW", + "cache_control": "private, no-store" + }, + { + "index": 15, + "status": 200, + "ttfb_ms": 246.0, + "after_connection_ms": 162.7, + "server_timing": "cfEdge;dur=27,cfOrigin;dur=0,cfWorker;dur=94", + "ray": "a4b291ffcac58c20-DFW", + "cache_control": "private, no-store" + }, + { + "index": 16, + "status": 200, + "ttfb_ms": 1290.9, + "after_connection_ms": 1208.5, + "server_timing": "cfEdge;dur=730,cfOrigin;dur=0,cfWorker;dur=437", + "ray": "a4b29203dbdbf0a6-DFW", + "cache_control": "private, no-store" + }, + { + "index": 17, + "status": 200, + "ttfb_ms": 449.5, + "after_connection_ms": 187.5, + "server_timing": "cfEdge;dur=21,cfOrigin;dur=0,cfWorker;dur=65", + "ray": "a4b2920fa9db5950-DFW", + "cache_control": "private, no-store" + }, + { + "index": 18, + "status": 200, + "ttfb_ms": 280.7, + "after_connection_ms": 165.7, + "server_timing": "cfEdge;dur=25,cfOrigin;dur=0,cfWorker;dur=97", + "ray": "a4b292145b9a2d33-DFW", + "cache_control": "private, no-store" + }, + { + "index": 19, + "status": 200, + "ttfb_ms": 205.0, + "after_connection_ms": 124.1, + "server_timing": "cfEdge;dur=38,cfOrigin;dur=0,cfWorker;dur=39", + "ray": "a4b292186c692d2f-DFW", + "cache_control": "private, no-store" + }, + { + "index": 20, + "status": 200, + "ttfb_ms": 254.8, + "after_connection_ms": 175.0, + "server_timing": "cfEdge;dur=35,cfOrigin;dur=0,cfWorker;dur=100", + "ray": "a4b2921c3bd446a5-DFW", + "cache_control": "private, no-store" + }, + { + "index": 21, + "status": 200, + "ttfb_ms": 409.4, + "after_connection_ms": 330.8, + "server_timing": "cfEdge;dur=39,cfOrigin;dur=0,cfWorker;dur=145", + "ray": "a4b292204e056c26-DFW", + "cache_control": "private, no-store" + }, + { + "index": 22, + "status": 200, + "ttfb_ms": 212.8, + "after_connection_ms": 134.2, + "server_timing": "cfEdge;dur=30,cfOrigin;dur=0,cfWorker;dur=61", + "ray": "a4b29225ae053da4-DFW", + "cache_control": "private, no-store" + }, + { + "index": 23, + "status": 200, + "ttfb_ms": 178.4, + "after_connection_ms": 98.3, + "server_timing": "cfEdge;dur=12,cfOrigin;dur=0,cfWorker;dur=46", + "ray": "a4b292298a79d57a-DFW", + "cache_control": "private, no-store" + } + ], + "settled": [ + { + "index": 0, + "status": 200, + "ttfb_ms": 339.1, + "after_connection_ms": 251.2, + "server_timing": "cfEdge;dur=47,cfOrigin;dur=0,cfWorker;dur=159", + "ray": "a4b2930b1d3f4773-DFW", + "cache_control": "private, no-store" + }, + { + "index": 1, + "status": 200, + "ttfb_ms": 190.2, + "after_connection_ms": 110.8, + "server_timing": "cfEdge;dur=24,cfOrigin;dur=0,cfWorker;dur=44", + "ray": "a4b2930fad79a922-DFW", + "cache_control": "private, no-store" + }, + { + "index": 2, + "status": 200, + "ttfb_ms": 279.5, + "after_connection_ms": 199.8, + "server_timing": "cfEdge;dur=42,cfOrigin;dur=0,cfWorker;dur=111", + "ray": "a4b293136f3b47a8-DFW", + "cache_control": "private, no-store" + }, + { + "index": 3, + "status": 200, + "ttfb_ms": 201.0, + "after_connection_ms": 119.6, + "server_timing": "cfEdge;dur=25,cfOrigin;dur=0,cfWorker;dur=53", + "ray": "a4b29317ae8f28f0-DFW", + "cache_control": "private, no-store" + }, + { + "index": 4, + "status": 200, + "ttfb_ms": 217.2, + "after_connection_ms": 136.5, + "server_timing": "cfEdge;dur=40,cfOrigin;dur=0,cfWorker;dur=53", + "ray": "a4b2931b6dfc33ce-DFW", + "cache_control": "private, no-store" + }, + { + "index": 5, + "status": 200, + "ttfb_ms": 222.2, + "after_connection_ms": 139.4, + "server_timing": "cfEdge;dur=35,cfOrigin;dur=0,cfWorker;dur=60", + "ray": "a4b2931f595a2c97-DFW", + "cache_control": "private, no-store" + }, + { + "index": 6, + "status": 200, + "ttfb_ms": 171.8, + "after_connection_ms": 94.2, + "server_timing": "cfEdge;dur=9,cfOrigin;dur=0,cfWorker;dur=42", + "ray": "a4b293233fb8aad5-DFW", + "cache_control": "private, no-store" + }, + { + "index": 7, + "status": 200, + "ttfb_ms": 403.5, + "after_connection_ms": 319.1, + "server_timing": "cfEdge;dur=20,cfOrigin;dur=0,cfWorker;dur=256", + "ray": "a4b29326db8d74d7-DFW", + "cache_control": "private, no-store" + }, + { + "index": 8, + "status": 200, + "ttfb_ms": 522.5, + "after_connection_ms": 440.8, + "server_timing": "cfEdge;dur=29,cfOrigin;dur=0,cfWorker;dur=368", + "ray": "a4b2932beb1de922-DFW", + "cache_control": "private, no-store" + }, + { + "index": 9, + "status": 200, + "ttfb_ms": 223.7, + "after_connection_ms": 143.2, + "server_timing": "cfEdge;dur=17,cfOrigin;dur=0,cfWorker;dur=83", + "ray": "a4b29331bc9ccd08-DFW", + "cache_control": "private, no-store" + }, + { + "index": 10, + "status": 200, + "ttfb_ms": 297.9, + "after_connection_ms": 211.1, + "server_timing": "cfEdge;dur=41,cfOrigin;dur=0,cfWorker;dur=123", + "ray": "a4b29335bea36c53-DFW", + "cache_control": "private, no-store" + }, + { + "index": 11, + "status": 200, + "ttfb_ms": 333.3, + "after_connection_ms": 209.9, + "server_timing": "cfEdge;dur=19,cfOrigin;dur=0,cfWorker;dur=146", + "ray": "a4b2933a698bf065-DFW", + "cache_control": "private, no-store" + }, + { + "index": 12, + "status": 200, + "ttfb_ms": 196.1, + "after_connection_ms": 116.5, + "server_timing": "cfEdge;dur=32,cfOrigin;dur=0,cfWorker;dur=42", + "ray": "a4b2933ebfb392e6-DFW", + "cache_control": "private, no-store" + }, + { + "index": 13, + "status": 200, + "ttfb_ms": 219.4, + "after_connection_ms": 139.6, + "server_timing": "cfEdge;dur=46,cfOrigin;dur=0,cfWorker;dur=50", + "ray": "a4b293427c46a91e-DFW", + "cache_control": "private, no-store" + }, + { + "index": 14, + "status": 200, + "ttfb_ms": 238.3, + "after_connection_ms": 158.8, + "server_timing": "cfEdge;dur=34,cfOrigin;dur=0,cfWorker;dur=85", + "ray": "a4b29346499bf082-DFW", + "cache_control": "private, no-store" + }, + { + "index": 15, + "status": 200, + "ttfb_ms": 227.7, + "after_connection_ms": 146.2, + "server_timing": "cfEdge;dur=45,cfOrigin;dur=0,cfWorker;dur=59", + "ray": "a4b2934a4b4014ef-DFW", + "cache_control": "private, no-store" + }, + { + "index": 16, + "status": 200, + "ttfb_ms": 309.1, + "after_connection_ms": 227.7, + "server_timing": "cfEdge;dur=33,cfOrigin;dur=0,cfWorker;dur=150", + "ray": "a4b29351597c2c85-DFW", + "cache_control": "private, no-store" + }, + { + "index": 17, + "status": 200, + "ttfb_ms": 1354.3, + "after_connection_ms": 1272.2, + "server_timing": "cfEdge;dur=731,cfOrigin;dur=0,cfWorker;dur=497", + "ray": "a4b29355c93866e9-DFW", + "cache_control": "private, no-store" + }, + { + "index": 18, + "status": 200, + "ttfb_ms": 292.0, + "after_connection_ms": 212.4, + "server_timing": "cfEdge;dur=27,cfOrigin;dur=0,cfWorker;dur=143", + "ray": "a4b29360c8c069f8-DFW", + "cache_control": "private, no-store" + }, + { + "index": 19, + "status": 200, + "ttfb_ms": 226.7, + "after_connection_ms": 143.5, + "server_timing": "cfEdge;dur=25,cfOrigin;dur=0,cfWorker;dur=76", + "ray": "a4b293653b47b901-DFW", + "cache_control": "private, no-store" + }, + { + "index": 20, + "status": 200, + "ttfb_ms": 311.0, + "after_connection_ms": 225.3, + "server_timing": "cfEdge;dur=51,cfOrigin;dur=0,cfWorker;dur=120", + "ray": "a4b293692d306bc2-DFW", + "cache_control": "private, no-store" + }, + { + "index": 21, + "status": 200, + "ttfb_ms": 211.4, + "after_connection_ms": 130.1, + "server_timing": "cfEdge;dur=16,cfOrigin;dur=0,cfWorker;dur=67", + "ray": "a4b2936dcd02ca89-DFW", + "cache_control": "private, no-store" + }, + { + "index": 22, + "status": 200, + "ttfb_ms": 1352.0, + "after_connection_ms": 1263.3, + "server_timing": "cfEdge;dur=740,cfOrigin;dur=0,cfWorker;dur=477", + "ray": "a4b29371bf51eb2b-DFW", + "cache_control": "private, no-store" + }, + { + "index": 23, + "status": 200, + "ttfb_ms": 258.7, + "after_connection_ms": 177.6, + "server_timing": "cfEdge;dur=29,cfOrigin;dur=0,cfWorker;dur=65", + "ray": "a4b2937cacd28b23-DFW", + "cache_control": "private, no-store" + } + ] + }, + "smoke_checks": [ + { + "path": "/api/chat/bootstrap", + "status": 401, + "contentType": "application/json", + "bytes": 32 + }, + { + "path": "/auth/mcp/client-metadata", + "status": 200, + "contentType": "application/json", + "bytes": 320 + }, + { + "path": "/api/og/query.png", + "status": 200, + "contentType": "image/png", + "bytes": 25500, + "png": true + }, + { + "path": "/api/readme/query.png?framework=react", + "status": 200, + "contentType": "image/png", + "bytes": 29752, + "png": true + }, + { + "path": "/api/application-starter/features?framework=react", + "status": 200, + "contentType": "application/json", + "bytes": 10980 + }, + { + "path": "/api/application-starter/deploy/check-name?name=worker-split-check", + "status": 401, + "contentType": "application/json", + "bytes": 47 + }, + { + "path": "/api/application-starter/compile", + "status": 200, + "fileCount": 16, + "hasPackageJson": true + } + ], + "browser": { + "status": 200, + "url": "https://tanstack.com/query/latest/docs/framework/react/installation", + "title": "Overview | TanStack Query React Docs", + "errors": [], + "chatRequests": [], + "documentNavigations": 1 + }, + "authentication": { + "before": { + "status": 401, + "bodyHash": "f49c9ec553f5bfed31a628d9cff7ea7475ce2b74969eaae86127e0fbd9575688" + }, + "after": { + "status": 401, + "bodyHash": "f49c9ec553f5bfed31a628d9cff7ea7475ce2b74969eaae86127e0fbd9575688" + }, + "saved_session_expired": true, + "successful_signed_in_check": "pending fresh login" + }, + "interpretation": "The settled production median is effectively unchanged. Fresh deployments and real production traffic produce a changing cold/warm mixture. These small sequential samples do not establish a production cold-TTFB percentile improvement. The separate controlled payload comparison demonstrates a reduction in preparation latency, not a 55% end-to-end docs speedup. Long responses remain." + } +} diff --git a/docs/worker-split-validation.md b/docs/worker-split-validation.md new file mode 100644 index 000000000..abf48228c --- /dev/null +++ b/docs/worker-split-validation.md @@ -0,0 +1,105 @@ +# Worker split validation + +The site Worker now excludes OG image generation, the chat HTTP backend and +Durable Objects, project starter templates, the chat app UI and the icon catalog. +One Start app and browser route tree remain. + +## Cold payload comparison + +Eighty interleaved requests compared the original and reduced site uploads with +an identical minimal handler. Neither handler imported the uploaded app modules, +used credentials, or accessed storage. Every request returned 200 from DFW. + +| | Original payload | Reduced payload | +| ------------------------------------------------ | ---------------: | --------------: | +| Upload | 26,260.77 KiB | 10,935.11 KiB | +| Gzip upload | 8,459.71 KiB | 3,158.83 KiB | +| First observed instance samples | 12 | 13 | +| Median first-instance response after connection | 1,048.05 ms | 471.6 ms | +| Median reused-instance response after connection | 67.65 ms | 65.2 ms | + +The upload shrank about 58%. Median first-instance latency fell about 55%, while +reused-instance latency stayed similar. This supports removing uploaded bytes +as a fix for the cold preparation delay found in the earlier investigation. + +These are payload preparation measurements, not full documentation page TTFB. +The handler's first observed request does not establish that every Cloudflare +platform cache was cold. The measurements still include network round-trip +time. Raw records and cleanup results are in `worker-split-measurements.json`. + +## Runtime and state checks + +The actual built app ran locally with all four Workers and native service +bindings, a synthetic session secret, and an unused local database address. + +- OG image generation returned a valid 1200 × 630 PNG. +- README image generation returned a valid 1800 × 450 PNG. +- Unknown libraries returned 404 and invalid README themes returned 400. +- Unauthenticated chat bootstrap returned 401, and MCP metadata returned 200. +- The icon browser rendered, filtered icons, and had no browser errors. +- Visiting chat without a session redirected to `/chat-access` without browser errors. +- All three chat Durable Object namespaces reported SQLite storage locally. +- Starter HTTP routing reached its existing database-backed rate limiter. Its + database-dependent checks could not complete with the synthetic local address. + +Disposable Cloudflare Workers exercised the same staged transfer as the +production deployment script: prepare the target, commit at the source, activate +the target's local bindings and existing workflow, then remove source tombstones. +The stored marker survived. A workflow created between transfer stages retained +its ID and completed successfully after reassignment. Four disposable Workers +and their test workflow were deleted afterward. + +The production build and full `pnpm test` suite pass: all TypeScript configurations, +lint, 568 unit tests, 2,270 chat tests and 20 desktop tests. Five existing tests +are skipped. + +The tests cover API path and method parity, route precedence, decoded parameters, +trailing slashes, HEAD requests, cookies, request bodies, streamed responses, +first-rollout ordering, ordinary deployments and preflight failure behavior. + +## Production rollout and response measurements + +Deployed to production on October 15, 2026. Initial split deployment version: +`57c7a11c-699b-4116-9cfa-2d3a9ceb9736`. + +The three Durable Object namespace IDs and the existing workflow ID match the +pre-deployment snapshot exactly. Their owner is now `tanstack-chat`. + +Production smoke checks passed for OG and README rendering, MCP metadata, +unauthenticated chat protection, starter feature discovery and in-memory project +generation. The generated project had 16 files including `package.json`, and no +repository or saved project was created. Docs rendered without browser errors +and navigated through the client router without another document request. No +chat APIs or chat app bundles loaded while visiting docs. + +Three sequential samples of the same public Query overview page, each with 24 +fresh curl connections from DFW, all returned 200: + +| Sample | Median TTFB | p95 TTFB, nearest rank | Maximum | Median cfWorker | +| ---------------------------- | ----------: | ---------------------: | ---------: | --------------: | +| Before deployment | 245.9 ms | 1,120.2 ms | 1,969.7 ms | 91 ms | +| Immediately after deployment | 305.5 ms | 1,293.4 ms | 1,511.8 ms | 100 ms | +| Follow-up after deployment | 248.5 ms | 1,352.0 ms | 1,354.3 ms | 84 ms | + +The settled median is effectively unchanged. These small samples have different +cold/warm mixtures and do not establish an improvement in production cold-TTFB +percentiles. Long responses remain. The controlled payload result above is not +a 55% end-to-end docs speedup. + +Documentation responses now explicitly use `private, no-store`. The prior +production version returned `public, max-age=0, must-revalidate`. No full-page +cache was enabled, and this rollout did not purge the CDN or run database schema +migrations. + +The user approved credential provisioning and the authenticated check. The +saved session was already expired before deployment and returned the same 401 +response afterward. A successful signed-in check still needs a fresh session +from `pnpm auth:login` in the primary repository. + +Automatic approval review rejected the earlier temporary public documentation +probes with a binding to the live cache. Those probes were not deployed; the +measurements above were made against the actual production site. + +The branch was subsequently rebased onto `b416acbc` to retain six newer main +commits, including the header branding and Hotkeys page updates. The response +samples above describe the initial split deployment. diff --git a/harness-tests/core/catalog-detail-http.test.ts b/harness-tests/core/catalog-detail-http.test.ts index 69a6ec338..2bfbef06a 100644 --- a/harness-tests/core/catalog-detail-http.test.ts +++ b/harness-tests/core/catalog-detail-http.test.ts @@ -29,8 +29,8 @@ vi.mock('~/chat/server/kody-environment.server', () => ({ })) vi.mock('~/chat/server/plugin-api', () => ({ pluginApi: m.plugins })) vi.mock('~/chat/server/skill-api', () => ({ skillApi: m.skills })) -import { handlePlugins } from '../../src/routes/api/chat/plugins' -import { handleSkills } from '../../src/routes/api/chat/skills' +import { handlePlugins } from '../../src/chat/server/routes/plugins' +import { handleSkills } from '../../src/chat/server/routes/skills' beforeEach(() => { vi.resetAllMocks() m.user.mockResolvedValue({ userId: 'owner', capabilities: ['builder'] }) diff --git a/harness-tests/core/chat-entrypoints.test.js b/harness-tests/core/chat-entrypoints.test.js new file mode 100644 index 000000000..bfe587ffe --- /dev/null +++ b/harness-tests/core/chat-entrypoints.test.js @@ -0,0 +1,120 @@ +import { expect, it, vi } from 'vitest' + +const state = vi.hoisted(() => ({ loaded: [], calls: [], initialized: [] })) +vi.mock('cloudflare:workers', () => ({ + DurableObject: class { + constructor(ctx, env) { + this.ctx = ctx + this.env = env + } + }, + WorkflowEntrypoint: class { + constructor(ctx, env) { + this.ctx = ctx + this.env = env + } + }, +})) +vi.mock('~/chat/server/conversation', () => { + state.loaded.push('conversation') + return { + Conversation: class { + constructor(ctx, env) { + state.initialized.push([ctx, env]) + ctx.blockConcurrencyWhile(async () => { + state.initialized.push('storage ready') + }) + } + begin(...args) { + state.calls.push(args) + return 'started' + } + alarm() { + return 'alarm complete' + } + }, + } +}) +vi.mock('~/chat/server/workspace-sync-object', () => { + state.loaded.push('workspace') + return { + WorkspaceSync: class { + snapshot(...args) { + state.calls.push(args) + return 'snapshot' + } + }, + } +}) +vi.mock('@durable-streams/server-cloudflare', () => { + state.loaded.push('streams') + return { + StreamObject: class { + fetch(request) { + return new Response(request.url) + } + forkAcquire(...args) { + state.calls.push(args) + return { ok: true } + } + }, + } +}) +vi.mock('~/chat/server/workflow-driver', () => { + state.loaded.push('workflow') + return { + TanChatWorkflow: class { + constructor(ctx, env) { + state.initialized.push([ctx, env]) + } + run(...args) { + state.calls.push(args) + return 'completed' + } + }, + } +}) + +it('loads implementations on object creation and preserves arguments, results, and initialization', async () => { + const entries = await import('~/chat/server/entries') + expect(state.loaded).toEqual([]) + const pending = [] + const ctx = { + blockConcurrencyWhile: (work) => { + const task = work() + pending.push(task) + return task + }, + } + const env = { marker: 'same environment' } + const conversation = new entries.Conversation(ctx, env) + await Promise.all(pending) + expect(state.loaded).toEqual(['conversation']) + expect(state.initialized).toEqual([[ctx, env], 'storage ready']) + const input = { messageId: 'message' } + expect(await conversation.begin(input)).toBe('started') + expect(state.calls.at(-1)).toEqual([input]) + expect(await conversation.alarm()).toBe('alarm complete') + + const workspace = new entries.WorkspaceSync(ctx, env) + await Promise.all(pending) + expect(await workspace.snapshot('workspace', 'user')).toBe('snapshot') + expect(state.calls.at(-1)).toEqual(['workspace', 'user']) + const stream = new entries.StreamObject(ctx, env) + await Promise.all(pending) + const request = new Request('https://example.com/stream') + expect(await (await stream.fetch(request)).text()).toBe(request.url) + expect(await stream.forkAcquire({ edgeId: 'edge' })).toEqual({ ok: true }) + const workflow = new entries.TanChatWorkflow(ctx, env) + expect(state.loaded).toEqual(['conversation', 'workspace', 'streams']) + const event = { payload: {} }, + step = {} + expect(await workflow.run(event, step)).toBe('completed') + expect(state.calls.at(-1)).toEqual([event, step]) + expect(state.loaded).toEqual([ + 'conversation', + 'workspace', + 'streams', + 'workflow', + ]) +}) diff --git a/harness-tests/core/chat-file-route.test.ts b/harness-tests/core/chat-file-route.test.ts index a11f83afa..e39b51f64 100644 --- a/harness-tests/core/chat-file-route.test.ts +++ b/harness-tests/core/chat-file-route.test.ts @@ -17,7 +17,7 @@ vi.mock('~/chat/server/file-api', () => ({ fileApi: dependencies.files })) vi.mock('~/chat/server/file-environment.server', () => ({ getFileEnvironment: dependencies.environment, })) -import { handleFileRequest } from '../../src/routes/api/chat/files' +import { handleFileRequest } from '../../src/chat/server/routes/files' import { SavedFileError } from '../../src/chat/server/saved-file-contract' const request = ( query = 'workspaceId=personal&botId=assistant', diff --git a/harness-tests/core/chat-mcp-route.test.ts b/harness-tests/core/chat-mcp-route.test.ts index ed64e4d8a..080d7199c 100644 --- a/harness-tests/core/chat-mcp-route.test.ts +++ b/harness-tests/core/chat-mcp-route.test.ts @@ -23,7 +23,7 @@ vi.mock('~/chat/server/mcp-environment.server', async (original) => ({ getMcpEnvironment: dep.env, })) vi.mock('~/chat/server/mcp-account-api', () => ({ mcpAccountApi: dep.api })) -import { handleMcp } from '../../src/routes/api/chat/mcp/$' +import { handleMcp } from '../../src/chat/server/routes/mcp/$' import { WorkspacePolicyError } from '../../src/chat/workspace-policy.server' beforeEach(() => { vi.clearAllMocks() diff --git a/harness-tests/core/chat-model-route.test.ts b/harness-tests/core/chat-model-route.test.ts index 4b5f7f71e..119812fba 100644 --- a/harness-tests/core/chat-model-route.test.ts +++ b/harness-tests/core/chat-model-route.test.ts @@ -25,7 +25,7 @@ vi.mock('~/chat/server/run-models', async (importOriginal) => ({ ...(await importOriginal()), getRunModelCatalog: dependencies.catalog, })) -import { handleModelCatalog } from '../../src/routes/api/chat/models' +import { handleModelCatalog } from '../../src/chat/server/routes/models' import { WorkspacePolicyError } from '../../src/chat/workspace-policy.server' import { RunModelError } from '../../src/chat/server/run-models' import { defaultPolicy } from '../../src/chat/core/types' diff --git a/harness-tests/core/chat-plugin-route.test.ts b/harness-tests/core/chat-plugin-route.test.ts index 32f804ad0..fe54150fc 100644 --- a/harness-tests/core/chat-plugin-route.test.ts +++ b/harness-tests/core/chat-plugin-route.test.ts @@ -23,7 +23,7 @@ vi.mock('~/chat/server/kody-environment.server', async (original) => ({ getKodyEnvironment: dep.env, })) vi.mock('~/chat/server/plugin-api', () => ({ pluginApi: dep.api })) -import { handlePlugins } from '../../src/routes/api/chat/plugins' +import { handlePlugins } from '../../src/chat/server/routes/plugins' import { WorkspacePolicyError } from '../../src/chat/workspace-policy.server' beforeEach(() => { vi.clearAllMocks() diff --git a/harness-tests/core/chat-runtime-headers.test.ts b/harness-tests/core/chat-runtime-headers.test.ts index 8c5fb0293..ebdbae256 100644 --- a/harness-tests/core/chat-runtime-headers.test.ts +++ b/harness-tests/core/chat-runtime-headers.test.ts @@ -9,8 +9,8 @@ const captured = vi.hoisted(() => { }) => Promise)[] = [] return { headers, beforeLoads } }) -vi.mock('../../src/chat/components/ChatShell', () => ({ - ChatShell: () => null, +vi.mock('../../src/chat/components/ChatShellRoute', () => ({ + ChatShellRoute: () => null, })) vi.mock('@tanstack/react-start', () => ({ createServerFn: () => ({ handler: () => () => null }), diff --git a/harness-tests/core/chat-skill-route.test.ts b/harness-tests/core/chat-skill-route.test.ts index 76749be36..5fce60ad2 100644 --- a/harness-tests/core/chat-skill-route.test.ts +++ b/harness-tests/core/chat-skill-route.test.ts @@ -23,7 +23,7 @@ vi.mock('~/chat/server/kody-environment.server', async (original) => ({ getKodyEnvironment: dep.env, })) vi.mock('~/chat/server/skill-api', () => ({ skillApi: dep.api })) -import { handleSkills } from '../../src/routes/api/chat/skills' +import { handleSkills } from '../../src/chat/server/routes/skills' import { WorkspacePolicyError } from '../../src/chat/workspace-policy.server' beforeEach(() => { vi.clearAllMocks() diff --git a/package.json b/package.json index 2c157137b..42fdc9ce1 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "preview": "vite preview", "preview:cloudflare": "pnpm run preview", "eval:builder-ai": "tsx scripts/builder-ai-eval/run.ts", - "deploy:cloudflare": "pnpm run db:migrate && pnpm run db:verify-builder && pnpm run db:verify-chat && pnpm run build:cloudflare && wrangler deploy --name tanstack-com && pnpm run purge:cloudflare", - "deploy:cloudflare:ci": "pnpm run db:prepare-builder && wrangler deploy --name tanstack-com", + "deploy:cloudflare": "node -e \"throw new Error('Production deployment is disabled on this staging experiment branch')\"", + "deploy:cloudflare:ci": "node -e \"throw new Error('Production deployment is disabled on this staging experiment branch')\"", "purge:cloudflare": "pnpm run with-env -- node scripts/purge-cloudflare-cache.mjs", "cf:typegen": "wrangler types", "content:build": "node scripts/build-content-collections.mjs", @@ -56,7 +56,10 @@ "finalize:desktop-release": "node scripts/desktop-release.mjs", "publish:desktop-release": "pnpm verify:desktop-release && node scripts/desktop-publish.mjs", "benchmark:chat-copy": "tsx scripts/chat/benchmark-conversation-copy.ts", - "benchmark:chat-protocol": "tsx scripts/chat/benchmark-conversation-protocol.ts" + "benchmark:chat-protocol": "tsx scripts/chat/benchmark-conversation-protocol.ts", + "deploy:cloudflare:workers": "node -e \"throw new Error('Production deployment is disabled on this staging experiment branch')\"", + "build:staging": "node scripts/prepare-worker-staging.mjs && vite build --mode staging", + "deploy:staging": "node scripts/deploy-worker-staging.mjs" }, "dependencies": { "@codemirror/lang-css": "^6.3.1", diff --git a/scripts/build-worker-staging-baseline.mjs b/scripts/build-worker-staging-baseline.mjs new file mode 100644 index 000000000..c4a95e46e --- /dev/null +++ b/scripts/build-worker-staging-baseline.mjs @@ -0,0 +1,38 @@ +import { spawnSync } from 'node:child_process' +import { mkdtempSync, symlinkSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +const root = process.cwd() +const directory = mkdtempSync(path.join(tmpdir(), 'tanstack-staging-baseline-')) +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: directory, + stdio: 'inherit', + ...options, + }) + if (result.error) throw result.error + if (result.status !== 0) throw new Error(`${command} failed`) + return result +} +const archive = run( + 'git', + ['-C', root, 'archive', '5cd9f45ac1685200fa413e74199392b2c3df23e1'], + { stdio: ['ignore', 'pipe', 'inherit'], maxBuffer: 256 * 1024 * 1024 }, +) +run('tar', ['-x', '-C', directory], { + input: archive.stdout, + stdio: ['pipe', 'inherit', 'inherit'], +}) +run('git', ['apply', path.join(root, 'staging/baseline.patch')]) +symlinkSync( + path.join(root, 'node_modules'), + path.join(directory, 'node_modules'), + 'dir', +) +run(process.execPath, [ + path.join(root, 'node_modules/vite/bin/vite.js'), + 'build', + '--mode', + 'staging', +]) +console.log(`Unified staging build: ${directory}/dist/server/wrangler.json`) diff --git a/scripts/chat-isolation-vite.ts b/scripts/chat-isolation-vite.ts new file mode 100644 index 000000000..f186d09df --- /dev/null +++ b/scripts/chat-isolation-vite.ts @@ -0,0 +1,58 @@ +import type { PluginOption } from 'vite' + +// Route matching needs synchronous search validation. Cloudflare also requires +// its exported classes at startup. Neither may import the chat implementation. +const eagerChatModules = new Set([ + 'core/navigation.ts', + 'core/workspace-panels.ts', + 'server/entries.ts', +]) + +export function chatIsolation(): PluginOption { + return { + name: 'tanstack-chat-isolation', + apply: 'build', + applyToEnvironment: (environment) => + ['client', 'ssr'].includes(environment.name), + generateBundle(_options, bundle) { + const chunks = Object.values(bundle).filter( + (item) => item.type === 'chunk', + ) + const byName = new Map(chunks.map((chunk) => [chunk.fileName, chunk])) + const pending = chunks + .filter( + (chunk) => + chunk.isEntry || + Object.keys(chunk.modules).some((id) => + id.endsWith('/src/router.tsx'), + ), + ) + .map((chunk) => chunk.fileName) + const visited = new Set() + const violations = new Set() + while (pending.length) { + const name = pending.pop()! + if (visited.has(name)) continue + visited.add(name) + const chunk = byName.get(name) + if (!chunk) continue + pending.push(...chunk.imports) + for (const [id, module] of Object.entries(chunk.modules)) { + const chatPath = id.split('/src/chat/')[1] + if ( + module.renderedLength && + chatPath && + !eagerChatModules.has(chatPath) + ) { + violations.add(chatPath) + } + } + } + if (violations.size) { + this.error( + `TanChat must load on demand. Eager modules:\n${[...violations].sort().join('\n')}`, + ) + } + }, + } +} diff --git a/scripts/deploy-worker-staging.mjs b/scripts/deploy-worker-staging.mjs new file mode 100644 index 000000000..74beec49d --- /dev/null +++ b/scripts/deploy-worker-staging.mjs @@ -0,0 +1,67 @@ +import { spawnSync } from 'node:child_process' +import { readFileSync } from 'node:fs' + +// This experiment can only target the isolated staging fleet. +const prefix = 'tanstack-perf-stage-' +const configurations = ['og', 'starter', 'chat', 'server'].map((kind) => { + const file = `dist/${kind}/wrangler.json` + return { + file, + expectedName: `${prefix}${kind === 'server' ? 'site' : kind}`, + config: JSON.parse(readFileSync(file, 'utf8')), + } +}) +const names = new Set(configurations.map(({ config }) => config.name)) +for (const { config, expectedName } of configurations) { + if ( + config.name !== expectedName || + config.routes?.length || + config.triggers?.crons?.length + ) + throw new Error(`Refusing non-staging destination: ${config.name}`) + if (config.hyperdrive?.length || config.ai || config.migrations?.length) + throw new Error(`Refusing shared resources or migrations: ${config.name}`) + for (const binding of config.r2_buckets ?? []) + if (!binding.bucket_name.startsWith(prefix)) + throw new Error('Non-staging R2 bucket') + for (const binding of config.services ?? []) + if (!names.has(binding.service)) + throw new Error('Non-staging service binding') + for (const binding of config.durable_objects?.bindings ?? []) + if (binding.script_name && !names.has(binding.script_name)) + throw new Error('Non-staging Durable Object binding') + for (const binding of config.workflows ?? []) + if ( + !binding.name.startsWith(prefix) || + (binding.script_name && !names.has(binding.script_name)) + ) + throw new Error('Non-staging workflow') + for (const entry of Object.values(config.exports ?? {})) + if (entry.state || entry.transfer_from || entry.transferred_to) + throw new Error('Namespace transfers are disabled in staging deploys') +} + +// These variables are scoped to one connected Cloudflare Builds Worker. +// Passing them to a different Worker overrides even an explicit --name. +const env = { ...process.env } +delete env.WRANGLER_CI_OVERRIDE_NAME +delete env.WRANGLER_CI_MATCH_TAG +for (const { file, config } of configurations) { + console.log(`Deploying staging Worker ${config.name}`) + const result = spawnSync( + 'pnpm', + [ + 'exec', + 'wrangler', + 'deploy', + '--config', + file, + '--name', + config.name, + '--keep-vars', + ], + { env, stdio: 'inherit' }, + ) + if (result.error) throw result.error + if (result.status !== 0) throw new Error(`Deployment failed: ${config.name}`) +} diff --git a/scripts/measure-worker-staging.py b/scripts/measure-worker-staging.py new file mode 100644 index 000000000..45154c942 --- /dev/null +++ b/scripts/measure-worker-staging.py @@ -0,0 +1,42 @@ +import json +import re +import subprocess +import tempfile +import time +from pathlib import Path + +origins = { + 'unified': 'https://tanstack-perf-stage-baseline.thetanstack.workers.dev', + 'split': 'https://tanstack-perf-stage-site.thetanstack.workers.dev', +} +records = [] +with tempfile.TemporaryDirectory() as directory: + headers = Path(directory) / 'headers' + for index in range(40): + order = list(origins) if index % 2 == 0 else list(reversed(origins)) + for kind in order: + url = origins[kind] + '/query/latest/docs/framework/react/overview' + result = subprocess.run([ + 'curl', '-sS', '--max-time', '45', '-D', str(headers), '-o', '/dev/null', + '-w', '%{json}', url, + ], capture_output=True, text=True, check=True) + timing = json.loads(result.stdout) + text = headers.read_text() + server = re.search(r'(?im)^server-timing:\s*(.*)', text) + counter = re.search(r'docs_request;desc="(\d+)"', server.group(1)) if server else None + ray = re.search(r'(?im)^cf-ray:\s*(.*)', text) + record = { + 'kind': kind, 'sample': index, 'status': timing['http_code'], + 'ttfb_ms': timing['time_starttransfer'] * 1000, + 'after_connection_ms': (timing['time_starttransfer'] - timing['time_appconnect']) * 1000, + 'total_ms': timing['time_total'] * 1000, + 'docs_request': int(counter.group(1)) if counter else None, + 'server_timing': server.group(1).strip() if server else None, + 'ray': ray.group(1).strip() if ray else None, + } + records.append(record) + print(json.dumps(record), flush=True) + if record['status'] != 200: + raise RuntimeError('Docs failed, refusing to report a performance comparison') + time.sleep(0.25) +Path('staging/measurements.json').write_text(json.dumps(records, indent=2) + '\n') diff --git a/scripts/prepare-worker-staging.mjs b/scripts/prepare-worker-staging.mjs new file mode 100644 index 000000000..6b8b9ab14 --- /dev/null +++ b/scripts/prepare-worker-staging.mjs @@ -0,0 +1,48 @@ +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs' +import path from 'node:path' +import ts from 'typescript' + +export const prefix = 'tanstack-perf-stage' +export const buckets = ['github', 'npm', 'files'].map( + (name) => `${prefix}-${name}`, +) +const root = process.cwd() +mkdirSync('.staging', { recursive: true }) +for (const [kind, source] of Object.entries({ + server: 'wrangler.jsonc', + og: 'workers/og/wrangler.jsonc', + starter: 'workers/starter/wrangler.jsonc', + chat: 'workers/chat/wrangler.jsonc', +})) { + const { config, error } = ts.parseConfigFileTextToJson( + source, + readFileSync(source, 'utf8'), + ) + if (error) throw new Error(`Invalid ${source}`) + config.name = `${prefix}-${kind === 'server' ? 'site' : kind}` + config.main = path.resolve(path.dirname(source), config.main) + config.workers_dev = kind === 'server' + config.preview_urls = false + config.routes = [] + config.triggers = { crons: [] } + config.hyperdrive = [] + delete config.ai + config.secrets = { required: ['SESSION_SECRET', 'ENCRYPTION_KEY'] } + config.vars = { APP_MODE: 'production', SENTRY_DSN: '' } + config.r2_buckets = (config.r2_buckets ?? []).map((binding) => ({ + binding: binding.binding, + bucket_name: `${prefix}-${binding.binding === 'GITHUB_CONTENT_CACHE' ? 'github' : binding.binding === 'NPM_DOWNLOAD_CACHE' ? 'npm' : 'files'}`, + })) + config.services = (config.services ?? []).map((binding) => ({ + binding: binding.binding, + service: `${prefix}-${binding.binding === 'ASSETS' ? 'site' : binding.binding.toLowerCase()}`, + })) + for (const binding of config.durable_objects?.bindings ?? []) + delete binding.script_name + for (const binding of config.workflows ?? []) { + binding.name = `${prefix}-workflow` + delete binding.script_name + } + if (config.assets) config.assets.directory = path.join(root, 'public') + writeFileSync(`.staging/${kind}.json`, JSON.stringify(config, null, 2)) +} diff --git a/scripts/worker-isolation-vite.ts b/scripts/worker-isolation-vite.ts new file mode 100644 index 000000000..16d9b9ee7 --- /dev/null +++ b/scripts/worker-isolation-vite.ts @@ -0,0 +1,48 @@ +import type { PluginOption } from 'vite' + +// Check every uploaded module, including lazy chunks. Lazy loading alone does +// not remove a dependency from a Cloudflare Worker deployment. +const excludedSiteModules = [ + '/src/server/og/generate.server.ts', + '/src/chat/server/conversation.ts', + '/src/chat/server/run-models.ts', + '/src/chat/server/workflow-driver.ts', + '/src/chat/server/workspace-sync-object.ts', + '/src/chat/components/App.tsx', + '/src/components/ds/phosphor-icons.generated.ts', + '/src/application-starter/api/create-worker.ts', + '/src/chat/server/http/', + '/src/application-starter/api/http/', + '/node_modules/@tanstack/create/', +] + +export function workerIsolation(): PluginOption { + return { + name: 'tanstack-worker-isolation', + apply: 'build', + applyToEnvironment: (environment) => environment.name === 'ssr', + generateBundle(_options, bundle) { + const violations = new Set() + for (const item of Object.values(bundle)) { + if (item.type !== 'chunk') continue + for (const [id, module] of Object.entries(item.modules)) { + // The site's existing example deployment API uses these six small provider templates. + const providerTemplate = + /\/generated\/worker\/frameworks\/(react|solid)\/add-ons\/(cloudflare|netlify|railway)\.js$/.test( + id, + ) + if ( + module.renderedLength && + !providerTemplate && + excludedSiteModules.some((path) => id.includes(path)) + ) + violations.add(id) + } + } + if (violations.size) + this.error( + `Heavy features must stay out of the site Worker:\n${[...violations].sort().join('\n')}`, + ) + }, + } +} diff --git a/src/application-starter/api/http/compile-attributed.ts b/src/application-starter/api/http/compile-attributed.ts new file mode 100644 index 000000000..c0a559182 --- /dev/null +++ b/src/application-starter/api/http/compile-attributed.ts @@ -0,0 +1,52 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + applicationStarterErrorResponse, + applicationStarterInternalErrorResponse, + applicationStarterJsonResponse, + readApplicationStarterJsonRequest, +} from '~/application-starter/api/request-boundary.server' +import { + applicationStarterValidateBodySchema, + parseApplicationStarterRequest, +} from '~/application-starter/api/request-schema.server' + +export default defineHttpRoute( + '/api/application-starter/compile-attributed', + z.object({}), + { + POST: async ({ request }: { request: Request }) => { + try { + const requestBody = await readApplicationStarterJsonRequest(request) + if ('response' in requestBody) { + return requestBody.response + } + + let body + try { + body = parseApplicationStarterRequest( + applicationStarterValidateBodySchema, + requestBody.body, + ) + } catch { + return applicationStarterErrorResponse( + 'Invalid request body', + 400, + requestBody.rateLimit, + ) + } + + const { compileWithAttributionHandler } = + await import('~/application-starter/api/compile') + const response = await compileWithAttributionHandler(body.definition) + + return applicationStarterJsonResponse(response, requestBody.rateLimit) + } catch (error) { + console.error('Error compiling project with attribution:', error) + return applicationStarterInternalErrorResponse( + 'Failed to compile project', + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/compile.ts b/src/application-starter/api/http/compile.ts new file mode 100644 index 000000000..123bb4643 --- /dev/null +++ b/src/application-starter/api/http/compile.ts @@ -0,0 +1,53 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + applicationStarterErrorResponse, + applicationStarterInternalErrorResponse, + applicationStarterJsonResponse, + readApplicationStarterJsonRequest, +} from '~/application-starter/api/request-boundary.server' +import { + applicationStarterCompileBodySchema, + parseApplicationStarterRequest, +} from '~/application-starter/api/request-schema.server' + +export default defineHttpRoute( + '/api/application-starter/compile', + z.object({}), + { + POST: async ({ request }: { request: Request }) => { + try { + const requestBody = await readApplicationStarterJsonRequest(request) + if ('response' in requestBody) { + return requestBody.response + } + + let body + try { + body = parseApplicationStarterRequest( + applicationStarterCompileBodySchema, + requestBody.body, + ) + } catch { + return applicationStarterErrorResponse( + 'Invalid request body', + 400, + requestBody.rateLimit, + ) + } + + const { compileHandler } = + await import('~/application-starter/api/compile') + const response = await compileHandler(body.definition, { + format: body.format, + }) + return applicationStarterJsonResponse(response, requestBody.rateLimit) + } catch (error) { + console.error('Error compiling project:', error) + return applicationStarterInternalErrorResponse( + 'Failed to compile project', + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/deploy/check-name.ts b/src/application-starter/api/http/deploy/check-name.ts new file mode 100644 index 000000000..5dbac3348 --- /dev/null +++ b/src/application-starter/api/http/deploy/check-name.ts @@ -0,0 +1,85 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { validateApplicationStarterGetRequest } from '~/application-starter/api/request-boundary.server' +import { jsonResponse } from '~/utils/api-boundary.server' +import { RATE_LIMITS } from '~/utils/rateLimit.server' + +export default defineHttpRoute( + '/api/application-starter/deploy/check-name', + z.object({}), + { + GET: async ({ request }: { request: Request }) => { + const requestGuard = await validateApplicationStarterGetRequest(request, { + rateLimit: RATE_LIMITS.deploy, + }) + if ('response' in requestGuard) { + return requestGuard.response + } + + const [ + { getAuthService }, + { getGitHubAuthState }, + { checkRepoNameAvailable, validateRepoName }, + ] = await Promise.all([ + import('~/auth/index.server'), + import('~/auth/github.server'), + import('~/utils/github-repo.server'), + ]) + const url = new URL(request.url) + const name = url.searchParams.get('name') + + if (!name) { + return jsonResponse( + { available: false, error: 'Name required' }, + { status: 400, headers: requestGuard.rateLimit.headers }, + ) + } + + const validation = validateRepoName(name) + if (!validation.valid) { + return jsonResponse( + { + available: false, + error: validation.error ?? 'Invalid repository name', + }, + { status: 400, headers: requestGuard.rateLimit.headers }, + ) + } + + const authService = getAuthService() + const user = await authService.getCurrentUser(request) + + if (!user) { + return jsonResponse( + { available: false, error: 'Not authenticated' }, + { status: 401, headers: requestGuard.rateLimit.headers }, + ) + } + + const authState = await getGitHubAuthState(user.userId) + + if (!authState.hasRepoScope || !authState.accessToken) { + return jsonResponse( + { available: false, error: 'Missing scope' }, + { status: 403, headers: requestGuard.rateLimit.headers }, + ) + } + + try { + const available = await checkRepoNameAvailable( + authState.accessToken, + name, + ) + return jsonResponse( + { available }, + { headers: requestGuard.rateLimit.headers }, + ) + } catch { + return jsonResponse( + { available: false, error: 'Could not check repository name' }, + { status: 502, headers: requestGuard.rateLimit.headers }, + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/deploy/github.ts b/src/application-starter/api/http/deploy/github.ts new file mode 100644 index 000000000..5eb692916 --- /dev/null +++ b/src/application-starter/api/http/deploy/github.ts @@ -0,0 +1,341 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + jsonResponse, + readJsonBody, + validateJsonRequest, +} from '~/utils/api-boundary.server' +import { + applicationStarterDeployBodySchema, + parseApplicationStarterRequest, +} from '~/application-starter/api/request-schema.server' +import { + checkIpRateLimit, + rateLimitedResponse, + RATE_LIMITS, +} from '~/utils/rateLimit.server' + +interface DeployResponse { + success: true + repoUrl: string + owner: string + repoName: string +} + +interface DeployError { + success: false + error: string + code: + | 'NOT_AUTHENTICATED' + | 'NO_GITHUB_ACCOUNT' + | 'MISSING_REPO_SCOPE' + | 'INVALID_REPO_NAME' + | 'REPO_NAME_TAKEN' + | 'REPO_CREATION_FAILED' + | 'PUSH_FAILED' + | 'COMPILE_FAILED' + | 'INVALID_REQUEST' +} + +function deployErrorResponse( + error: DeployError, + status: number, + headers?: Headers, +) { + return jsonResponse(error, { status, headers }) +} + +export default defineHttpRoute( + '/api/application-starter/deploy/github', + z.object({}), + { + /** + * GET: Check GitHub auth state for current user + */ + GET: async ({ request }: { request: Request }) => { + const [{ getAuthService }, { getGitHubAuthState }] = await Promise.all([ + import('~/auth/index.server'), + import('~/auth/github.server'), + ]) + const authService = getAuthService() + const user = await authService.getCurrentUser(request) + + if (!user) { + return jsonResponse({ + authenticated: false, + hasGitHubAccount: false, + hasRepoScope: false, + }) + } + + const authState = await getGitHubAuthState(user.userId) + + return jsonResponse({ + authenticated: true, + ...authState, + }) + }, + + /** + * POST: Create GitHub repo and push project files + */ + POST: async ({ request }: { request: Request }) => { + const [authIndex, authGithub, githubRepo] = await Promise.all([ + import('~/auth/index.server'), + import('~/auth/github.server'), + import('~/utils/github-repo.server'), + ]) + const { getAuthService } = authIndex + const { getGitHubAuthState } = authGithub + const { + createRepository, + pushFiles, + validateRepoName, + validateGitHubFiles, + generateRepoDescription, + } = githubRepo + // Rate limiting (10 requests/minute per IP) + const rateLimit = await checkIpRateLimit(request, RATE_LIMITS.deploy) + if (!rateLimit.allowed) { + return rateLimitedResponse(rateLimit) + } + + const guardError = validateJsonRequest(request, { + maxContentLength: 1024 * 1024, + }) + if (guardError) { + return deployErrorResponse( + { + success: false, + error: guardError.message, + code: 'INVALID_REQUEST', + }, + guardError.status, + rateLimit.headers, + ) + } + + const authService = getAuthService() + const user = await authService.getCurrentUser(request) + + if (!user) { + return deployErrorResponse( + { + success: false, + error: 'You must be logged in to deploy', + code: 'NOT_AUTHENTICATED', + }, + 401, + rateLimit.headers, + ) + } + + const authState = await getGitHubAuthState(user.userId) + + if (!authState.hasGitHubAccount) { + return deployErrorResponse( + { + success: false, + error: 'No GitHub account linked', + code: 'NO_GITHUB_ACCOUNT', + }, + 403, + rateLimit.headers, + ) + } + + if (!authState.hasRepoScope || !authState.accessToken) { + console.log('[Deploy] Auth state check failed:', { + hasRepoScope: authState.hasRepoScope, + hasToken: !!authState.accessToken, + }) + return deployErrorResponse( + { + success: false, + error: + 'Missing public_repo scope. Please re-authenticate with GitHub.', + code: 'MISSING_REPO_SCOPE', + }, + 403, + rateLimit.headers, + ) + } + console.log( + '[Deploy] Auth state OK, hasRepoScope:', + authState.hasRepoScope, + ) + + let body + try { + const bodyResult = await readJsonBody(request, { + maxContentLength: 1024 * 1024, + }) + if (!bodyResult.success) { + throw new Error(bodyResult.error.message) + } + body = parseApplicationStarterRequest( + applicationStarterDeployBodySchema, + bodyResult.body, + ) + } catch { + return deployErrorResponse( + { + success: false, + error: 'Invalid request body', + code: 'INVALID_REQUEST', + }, + 400, + rateLimit.headers, + ) + } + + const { + repoName, + isPrivate, + projectName, + framework, + packageManager, + features, + featureOptions, + tailwind, + files, + } = body + const safeFeatureOptions = featureOptions ?? {} + + // Validate repo name + const validation = validateRepoName(repoName) + if (!validation.valid) { + return deployErrorResponse( + { + success: false, + error: validation.error ?? 'Invalid repository name', + code: 'INVALID_REPO_NAME', + }, + 400, + rateLimit.headers, + ) + } + + // Compile the project, or use files generated by the client-only applicationStarter. + let compiledFiles: Record + if (files) { + const fileValidation = validateGitHubFiles(files) + if (!fileValidation.valid) { + return deployErrorResponse( + { + success: false, + error: fileValidation.error, + code: 'INVALID_REQUEST', + }, + 400, + rateLimit.headers, + ) + } + compiledFiles = files + } else { + try { + const { compileHandler } = + await import('~/application-starter/api/compile') + const result = await compileHandler({ + name: projectName, + framework, + packageManager, + tailwind, + features, + featureOptions: safeFeatureOptions, + }) + compiledFiles = result.files + } catch (error) { + console.error('[Deploy] Compile failed:', error) + return deployErrorResponse( + { + success: false, + error: 'Failed to compile project', + code: 'COMPILE_FAILED', + }, + 500, + rateLimit.headers, + ) + } + } + + const compiledFileValidation = validateGitHubFiles(compiledFiles) + if (!compiledFileValidation.valid) { + return deployErrorResponse( + { + success: false, + error: compiledFileValidation.error, + code: 'INVALID_REQUEST', + }, + 400, + rateLimit.headers, + ) + } + + // Create the repository + const description = generateRepoDescription(features) + console.log('[Deploy] Creating repository:', { + repoName, + isPrivate, + description, + }) + const createResult = await createRepository(authState.accessToken, { + name: repoName, + description, + isPrivate, + }) + + if (!createResult.success) { + console.error('[Deploy] Repository creation failed:', { + error: createResult.error, + code: createResult.code, + }) + const code = + createResult.code === 'NAME_TAKEN' + ? 'REPO_NAME_TAKEN' + : 'REPO_CREATION_FAILED' + return deployErrorResponse( + { + success: false, + error: createResult.error, + code, + }, + code === 'REPO_NAME_TAKEN' ? 409 : 500, + rateLimit.headers, + ) + } + console.log('[Deploy] Repository created:', createResult.repoUrl) + + // Push files to the repository + const pushResult = await pushFiles(authState.accessToken, { + owner: createResult.owner, + repo: createResult.name, + files: compiledFiles, + message: 'Initial commit from TanStack Application Starter', + }) + + if (!pushResult.success) { + console.error('[Deploy] Push failed:', pushResult.error) + return deployErrorResponse( + { + success: false, + error: `Repository created but failed to push files: ${pushResult.error}`, + code: 'PUSH_FAILED', + }, + 500, + rateLimit.headers, + ) + } + + return jsonResponse( + { + success: true, + repoUrl: createResult.repoUrl, + owner: createResult.owner, + repoName: createResult.name, + } satisfies DeployResponse, + { headers: rateLimit.headers }, + ) + }, + }, +) diff --git a/src/application-starter/api/http/download.ts b/src/application-starter/api/http/download.ts new file mode 100644 index 000000000..7b65df10f --- /dev/null +++ b/src/application-starter/api/http/download.ts @@ -0,0 +1,144 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import JSZip from 'jszip' +import { + getContentDispositionHeader, + sanitizeResponseFilename, +} from '~/utils/http-response' +import { + applicationStarterErrorResponse, + applicationStarterInternalErrorResponse, + validateApplicationStarterGetRequest, +} from '~/application-starter/api/request-boundary.server' + +const BASE64_PREFIX = 'base64::' +const MAX_BUILDER_DOWNLOAD_FEATURES = 80 +const MAX_BUILDER_DOWNLOAD_OPTIONS = 200 +const BUILDER_ID_PATTERN = /^[a-z0-9._-]{1,120}$/i +const BUILDER_PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,119}$/ + +function decodeBase64File(content: string): Uint8Array | null { + if (content.startsWith(BASE64_PREFIX)) { + const decoded = atob(content.slice(BASE64_PREFIX.length)) + + return Uint8Array.from(decoded, (char) => char.charCodeAt(0)) + } + + return null +} + +export default defineHttpRoute( + '/api/application-starter/download', + z.object({}), + { + GET: async ({ request }: { request: Request }) => { + try { + const requestGuard = await validateApplicationStarterGetRequest(request) + if ('response' in requestGuard) { + return requestGuard.response + } + + const url = new URL(request.url) + const name = url.searchParams.get('name') || 'my-tanstack-app' + const featuresParam = url.searchParams.get('features') || '' + const framework = url.searchParams.get('framework') || 'react' + const packageManager = url.searchParams.get('pm') || 'pnpm' + const tailwind = url.searchParams.get('tailwind') !== 'false' + + const features = featuresParam + .split(',') + .map((f) => f.trim()) + .filter(Boolean) + + if ( + !BUILDER_PROJECT_NAME_PATTERN.test(name) || + features.length > MAX_BUILDER_DOWNLOAD_FEATURES || + features.some((feature) => !BUILDER_ID_PATTERN.test(feature)) + ) { + return applicationStarterErrorResponse( + 'Invalid applicationStarter download request', + 400, + requestGuard.rateLimit, + ) + } + + // Parse feature options (keys like "drizzle.database=postgres") + const featureOptions: Record> = {} + let optionCount = 0 + for (const [key, value] of url.searchParams.entries()) { + if (key.includes('.') && value) { + const [featureId, optionKey] = key.split('.') + optionCount++ + if ( + optionCount > MAX_BUILDER_DOWNLOAD_OPTIONS || + !BUILDER_ID_PATTERN.test(featureId) || + !BUILDER_ID_PATTERN.test(optionKey) || + value.length > 500 + ) { + return applicationStarterErrorResponse( + 'Invalid applicationStarter download options', + 400, + requestGuard.rateLimit, + ) + } + if (!featureOptions[featureId]) { + featureOptions[featureId] = {} + } + featureOptions[featureId][optionKey] = value + } + } + + const { compileHandler } = + await import('~/application-starter/api/compile') + const result = await compileHandler({ + name, + framework: framework === 'solid' ? 'solid' : 'react', + packageManager: + packageManager === 'bun' || + packageManager === 'npm' || + packageManager === 'yarn' + ? packageManager + : 'pnpm', + tailwind, + features, + featureOptions, + }) + + const zip = new JSZip() + const zipFilename = sanitizeResponseFilename(`${name}.zip`, 'app.zip') + const rootFolderName = sanitizeResponseFilename(name, 'app') + const rootFolder = zip.folder(rootFolderName) + if (!rootFolder) { + throw new Error('Failed to create ZIP folder') + } + + for (const [filePath, content] of Object.entries(result.files)) { + // Handle base64-encoded binary files (SVGs, images, etc.) + const binaryContent = decodeBase64File(content) + if (binaryContent) { + rootFolder.file(filePath, binaryContent, { binary: true }) + } else { + rootFolder.file(filePath, content) + } + } + + const blob = await zip.generateAsync({ type: 'arraybuffer' }) + + return new Response(blob, { + headers: { + 'Content-Type': 'application/zip', + 'Content-Disposition': getContentDispositionHeader( + 'attachment', + zipFilename, + 'app.zip', + ), + 'Cache-Control': 'no-store', + }, + }) + } catch (error) { + console.error('Error generating ZIP:', error) + return applicationStarterInternalErrorResponse('Failed to generate ZIP') + } + }, + }, +) diff --git a/src/application-starter/api/http/feature-artifacts.ts b/src/application-starter/api/http/feature-artifacts.ts new file mode 100644 index 000000000..466ffcfae --- /dev/null +++ b/src/application-starter/api/http/feature-artifacts.ts @@ -0,0 +1,59 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { normalizeFrameworkId } from '~/application-starter/frameworks' +import { + applicationStarterErrorResponse, + applicationStarterInternalErrorResponse, + applicationStarterJsonResponse, + readApplicationStarterJsonRequest, +} from '~/application-starter/api/request-boundary.server' +import { + applicationStarterFeatureArtifactsBodySchema, + parseApplicationStarterRequest, +} from '~/application-starter/api/request-schema.server' + +export default defineHttpRoute( + '/api/application-starter/feature-artifacts', + z.object({}), + { + POST: async ({ request }: { request: Request }) => { + try { + const requestBody = await readApplicationStarterJsonRequest(request) + if ('response' in requestBody) { + return requestBody.response + } + + let body + try { + body = parseApplicationStarterRequest( + applicationStarterFeatureArtifactsBodySchema, + requestBody.body, + ) + } catch { + return applicationStarterErrorResponse( + 'Invalid request body', + 400, + requestBody.rateLimit, + ) + } + + const { featureArtifactsHandler } = + await import('~/application-starter/api/feature-artifacts') + const response = await featureArtifactsHandler({ + features: body.features, + projectName: body.projectName, + framework: normalizeFrameworkId(body.framework), + featureOptions: body.featureOptions, + tailwind: body.tailwind, + }) + + return applicationStarterJsonResponse(response, requestBody.rateLimit) + } catch (error) { + console.error('Error fetching feature artifacts:', error) + return applicationStarterInternalErrorResponse( + 'Failed to fetch feature artifacts', + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/features.ts b/src/application-starter/api/http/features.ts new file mode 100644 index 000000000..217741cbd --- /dev/null +++ b/src/application-starter/api/http/features.ts @@ -0,0 +1,33 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { normalizeFrameworkId } from '~/application-starter/frameworks' + +export default defineHttpRoute( + '/api/application-starter/features', + z.object({}), + { + GET: async ({ request }: { request: Request }) => { + try { + const url = new URL(request.url) + const framework = normalizeFrameworkId( + url.searchParams.get('framework') ?? 'react', + ) + const { getFeaturesHandler } = + await import('~/application-starter/api/features') + const response = await getFeaturesHandler(framework) + return new Response(JSON.stringify(response), { + headers: { 'Content-Type': 'application/json' }, + }) + } catch (error) { + console.error('Error fetching features:', error) + return new Response( + JSON.stringify({ error: 'Failed to fetch features' }), + { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }, + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/load-remote-addon.ts b/src/application-starter/api/http/load-remote-addon.ts new file mode 100644 index 000000000..1ce8d88d6 --- /dev/null +++ b/src/application-starter/api/http/load-remote-addon.ts @@ -0,0 +1,39 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + applicationStarterErrorResponse, + applicationStarterJsonResponse, + validateApplicationStarterGetRequest, +} from '~/application-starter/api/request-boundary.server' +import { RATE_LIMITS } from '~/utils/rateLimit.server' + +export default defineHttpRoute( + '/api/application-starter/load-remote-addon', + z.object({}), + { + GET: async ({ request }: { request: Request }) => { + const requestGuard = await validateApplicationStarterGetRequest(request, { + rateLimit: RATE_LIMITS.applicationStarterRemote, + }) + if ('response' in requestGuard) { + return requestGuard.response + } + + const url = new URL(request.url) + const integrationUrl = url.searchParams.get('url') + + if (!integrationUrl) { + return applicationStarterErrorResponse( + 'URL is required', + 400, + requestGuard.rateLimit, + ) + } + + const { loadRemoteIntegrationHandler } = + await import('~/application-starter/api/remote') + const response = await loadRemoteIntegrationHandler(integrationUrl) + return applicationStarterJsonResponse(response, requestGuard.rateLimit) + }, + }, +) diff --git a/src/application-starter/api/http/load-remote-template.ts b/src/application-starter/api/http/load-remote-template.ts new file mode 100644 index 000000000..a0ee60998 --- /dev/null +++ b/src/application-starter/api/http/load-remote-template.ts @@ -0,0 +1,39 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + applicationStarterErrorResponse, + applicationStarterJsonResponse, + validateApplicationStarterGetRequest, +} from '~/application-starter/api/request-boundary.server' +import { RATE_LIMITS } from '~/utils/rateLimit.server' + +export default defineHttpRoute( + '/api/application-starter/load-remote-template', + z.object({}), + { + GET: async ({ request }: { request: Request }) => { + const requestGuard = await validateApplicationStarterGetRequest(request, { + rateLimit: RATE_LIMITS.applicationStarterRemote, + }) + if ('response' in requestGuard) { + return requestGuard.response + } + + const url = new URL(request.url) + const templateUrl = url.searchParams.get('url') + + if (!templateUrl) { + return applicationStarterErrorResponse( + 'URL is required', + 400, + requestGuard.rateLimit, + ) + } + + const { loadRemoteTemplateHandler } = + await import('~/application-starter/api/remote') + const response = await loadRemoteTemplateHandler(templateUrl) + return applicationStarterJsonResponse(response, requestGuard.rateLimit) + }, + }, +) diff --git a/src/application-starter/api/http/load-template.ts b/src/application-starter/api/http/load-template.ts new file mode 100644 index 000000000..75c8c92d8 --- /dev/null +++ b/src/application-starter/api/http/load-template.ts @@ -0,0 +1,54 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + applicationStarterErrorResponse, + applicationStarterJsonResponse, + readApplicationStarterJsonRequest, +} from '~/application-starter/api/request-boundary.server' +import { + applicationStarterRemoteLoadBodySchema, + parseApplicationStarterRequest, +} from '~/application-starter/api/request-schema.server' +import { RATE_LIMITS } from '~/utils/rateLimit.server' + +export default defineHttpRoute( + '/api/application-starter/load-template', + z.object({}), + { + POST: async ({ request }: { request: Request }) => { + try { + const requestBody = await readApplicationStarterJsonRequest(request, { + rateLimit: RATE_LIMITS.applicationStarterRemote, + }) + if ('response' in requestBody) { + return requestBody.response + } + + let body + try { + body = parseApplicationStarterRequest( + applicationStarterRemoteLoadBodySchema, + requestBody.body, + ) + } catch { + return applicationStarterErrorResponse( + 'Invalid request body', + 400, + requestBody.rateLimit, + ) + } + + const { loadRemoteTemplateHandler } = + await import('~/application-starter/api/remote') + const response = await loadRemoteTemplateHandler(body.url) + return applicationStarterJsonResponse(response, requestBody.rateLimit) + } catch (error) { + console.error('Error loading remote template:', error) + return applicationStarterErrorResponse( + 'Failed to load remote template', + 500, + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/resolve.ts b/src/application-starter/api/http/resolve.ts new file mode 100644 index 000000000..fec5771b5 --- /dev/null +++ b/src/application-starter/api/http/resolve.ts @@ -0,0 +1,207 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + checkIpWindowRateLimit, + checkIpRateLimit, + getIpWindowRateLimitStatus, + RATE_LIMITS, + rateLimitedResponse, +} from '~/utils/rateLimit.server' +import { envFunctions } from '~/utils/env.functions' +import { + isRecord, + readJsonBody, + validateJsonRequest, +} from '~/utils/api-boundary.server' + +const MAX_APPLICATION_STARTER_BODY_BYTES = 16 * 1024 + +export default defineHttpRoute( + '/api/application-starter/resolve', + z.object({}), + { + GET: async ({ request }: { request: Request }) => { + try { + if (!envFunctions.DATABASE_URL) { + return new Response( + JSON.stringify({ + authenticated: false, + anonymousGenerationQuota: { + limit: RATE_LIMITS.applicationStarterAnonymousDaily.limit, + remaining: RATE_LIMITS.applicationStarterAnonymousDaily.limit, + resetAt: new Date( + Date.now() + + RATE_LIMITS.applicationStarterAnonymousDaily.windowMs, + ).toISOString(), + }, + }), + { + headers: { + 'Cache-Control': 'no-store', + 'Content-Type': 'application/json', + }, + }, + ) + } + + const [{ getAuthService }, anonymousQuota] = await Promise.all([ + import('~/auth/index.server'), + getIpWindowRateLimitStatus( + request, + RATE_LIMITS.applicationStarterAnonymousDaily, + ), + ]) + + const authService = getAuthService() + const user = await authService.getCurrentUser(request) + + return new Response( + JSON.stringify({ + authenticated: !!user, + anonymousGenerationQuota: user + ? null + : { + limit: anonymousQuota.limit, + remaining: anonymousQuota.remaining, + resetAt: anonymousQuota.resetAt.toISOString(), + }, + }), + { + headers: { + 'Cache-Control': 'no-store', + 'Content-Type': 'application/json', + }, + }, + ) + } catch (error) { + console.error('Error loading application starter status:', error) + + return new Response( + JSON.stringify({ + error: 'Failed to load application starter status', + }), + { + status: 500, + headers: { + 'Cache-Control': 'no-store', + 'Content-Type': 'application/json', + }, + }, + ) + } + }, + POST: async ({ request }: { request: Request }) => { + try { + const [{ getAuthService }, starterServer] = await Promise.all([ + import('~/auth/index.server'), + import('~/utils/application-starter.server'), + ]) + + const rateLimit = await checkIpRateLimit( + request, + RATE_LIMITS.applicationStarter, + ) + + if (!rateLimit.allowed) { + return rateLimitedResponse(rateLimit) + } + + const requestGuardError = validateJsonRequest(request, { + maxContentLength: MAX_APPLICATION_STARTER_BODY_BYTES, + }) + if (requestGuardError) { + return new Response( + JSON.stringify({ error: requestGuardError.message }), + { + status: requestGuardError.status, + headers: { + 'Content-Type': 'application/json', + ...Object.fromEntries(rateLimit.headers.entries()), + }, + }, + ) + } + + const bodyResult = await readJsonBody(request, { + maxContentLength: MAX_APPLICATION_STARTER_BODY_BYTES, + }) + if (!bodyResult.success) { + return new Response( + JSON.stringify({ error: bodyResult.error.message }), + { + status: bodyResult.error.status, + headers: { + 'Content-Type': 'application/json', + ...Object.fromEntries(rateLimit.headers.entries()), + }, + }, + ) + } + + const rawBody = bodyResult.body + const isAnalyzeRequest = isRecord(rawBody) && rawBody.mode === 'analyze' + const body = + starterServer.applicationStarterRequestSchema.parse(rawBody) + + const authService = getAuthService() + const user = await authService.getCurrentUser(request) + + if (!user && !isAnalyzeRequest) { + const anonymousQuota = await checkIpWindowRateLimit( + request, + RATE_LIMITS.applicationStarterAnonymousDaily, + ) + + if (!anonymousQuota.allowed) { + return new Response( + JSON.stringify({ + code: 'LOGIN_REQUIRED_FOR_MORE_GENERATIONS', + error: 'Anonymous generation limit reached', + limit: RATE_LIMITS.applicationStarterAnonymousDaily.limit, + loginRequired: true, + retryAfter: Math.ceil( + (anonymousQuota.resetAt.getTime() - Date.now()) / 1000, + ), + }), + { + status: 429, + headers: { + 'Content-Type': 'application/json', + ...Object.fromEntries(rateLimit.headers.entries()), + ...Object.fromEntries(anonymousQuota.headers.entries()), + }, + }, + ) + } + } + + const response = isAnalyzeRequest + ? await starterServer.analyzeApplicationStarterServer({ + data: body, + }) + : await starterServer.resolveApplicationStarterServer({ + data: body, + }) + + return new Response(JSON.stringify(response), { + headers: { + 'Cache-Control': 'no-store', + 'Content-Type': 'application/json', + ...Object.fromEntries(rateLimit.headers.entries()), + }, + }) + } catch (error) { + console.error('Error resolving application starter:', error) + return new Response( + JSON.stringify({ + error: 'Failed to resolve application starter', + }), + { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }, + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/routes.ts b/src/application-starter/api/http/routes.ts new file mode 100644 index 000000000..ab1b98624 --- /dev/null +++ b/src/application-starter/api/http/routes.ts @@ -0,0 +1,29 @@ +import route0 from '~/application-starter/api/http/compile-attributed' +import route1 from '~/application-starter/api/http/compile' +import route2 from '~/application-starter/api/http/deploy/check-name' +import route3 from '~/application-starter/api/http/deploy/github' +import route4 from '~/application-starter/api/http/download' +import route5 from '~/application-starter/api/http/feature-artifacts' +import route6 from '~/application-starter/api/http/features' +import route7 from '~/application-starter/api/http/load-remote-addon' +import route8 from '~/application-starter/api/http/load-remote-template' +import route9 from '~/application-starter/api/http/load-template' +import route10 from '~/application-starter/api/http/resolve' +import route11 from '~/application-starter/api/http/suggest' +import route12 from '~/application-starter/api/http/validate' + +export const starterHttpRoutes = [ + route0, + route1, + route2, + route3, + route4, + route5, + route6, + route7, + route8, + route9, + route10, + route11, + route12, +] diff --git a/src/application-starter/api/http/suggest.ts b/src/application-starter/api/http/suggest.ts new file mode 100644 index 000000000..94e79cbd4 --- /dev/null +++ b/src/application-starter/api/http/suggest.ts @@ -0,0 +1,51 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + applicationStarterErrorResponse, + applicationStarterInternalErrorResponse, + applicationStarterJsonResponse, + readApplicationStarterJsonRequest, +} from '~/application-starter/api/request-boundary.server' +import { + applicationStarterSuggestBodySchema, + parseApplicationStarterRequest, +} from '~/application-starter/api/request-schema.server' + +export default defineHttpRoute( + '/api/application-starter/suggest', + z.object({}), + { + POST: async ({ request }: { request: Request }) => { + try { + const requestBody = await readApplicationStarterJsonRequest(request) + if ('response' in requestBody) { + return requestBody.response + } + + let body + try { + body = parseApplicationStarterRequest( + applicationStarterSuggestBodySchema, + requestBody.body, + ) + } catch { + return applicationStarterErrorResponse( + 'Invalid request body', + 400, + requestBody.rateLimit, + ) + } + + const { suggestHandler } = + await import('~/application-starter/api/suggest') + const response = await suggestHandler(body) + return applicationStarterJsonResponse(response, requestBody.rateLimit) + } catch (error) { + console.error('Error suggesting features:', error) + return applicationStarterInternalErrorResponse( + 'Failed to generate suggestions', + ) + } + }, + }, +) diff --git a/src/application-starter/api/http/validate.ts b/src/application-starter/api/http/validate.ts new file mode 100644 index 000000000..835d5bdae --- /dev/null +++ b/src/application-starter/api/http/validate.ts @@ -0,0 +1,51 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' +import { + applicationStarterErrorResponse, + applicationStarterInternalErrorResponse, + applicationStarterJsonResponse, + readApplicationStarterJsonRequest, +} from '~/application-starter/api/request-boundary.server' +import { + applicationStarterValidateBodySchema, + parseApplicationStarterRequest, +} from '~/application-starter/api/request-schema.server' + +export default defineHttpRoute( + '/api/application-starter/validate', + z.object({}), + { + POST: async ({ request }: { request: Request }) => { + try { + const requestBody = await readApplicationStarterJsonRequest(request) + if ('response' in requestBody) { + return requestBody.response + } + + let body + try { + body = parseApplicationStarterRequest( + applicationStarterValidateBodySchema, + requestBody.body, + ) + } catch { + return applicationStarterErrorResponse( + 'Invalid request body', + 400, + requestBody.rateLimit, + ) + } + + const { validateHandler } = + await import('~/application-starter/api/validate') + const response = await validateHandler(body.definition) + return applicationStarterJsonResponse(response, requestBody.rateLimit) + } catch (error) { + console.error('Error validating project:', error) + return applicationStarterInternalErrorResponse( + 'Failed to validate project', + ) + } + }, + }, +) diff --git a/src/application-starter/api/request-boundary.server.ts b/src/application-starter/api/request-boundary.server.ts index bd297b2ec..05581a3b3 100644 --- a/src/application-starter/api/request-boundary.server.ts +++ b/src/application-starter/api/request-boundary.server.ts @@ -59,7 +59,7 @@ export async function readApplicationStarterJsonRequest( export async function validateApplicationStarterGetRequest( request: Request, options: { rateLimit?: RateLimitOptions } = {}, -) { +): Promise<{ response: Response } | { rateLimit: RateLimitResult }> { const rateLimit = await checkIpRateLimit( request, options.rateLimit ?? RATE_LIMITS.applicationStarterCompile, diff --git a/src/chat/components/AppRoute.tsx b/src/chat/components/AppRoute.tsx new file mode 100644 index 000000000..1bd9183ee --- /dev/null +++ b/src/chat/components/AppRoute.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { createClientOnlyFn } from '@tanstack/react-start' +import { ClientOnly } from '@tanstack/react-router' +import { WorkspaceSkeleton } from './WorkspaceSkeleton' + +const LazyApp = React.lazy( + createClientOnlyFn(() => + import('./App').then((module) => ({ default: module.App })), + ), +) + +export function AppRoute() { + return ( + }> + }> + + + + ) +} diff --git a/src/chat/components/ChatShellRoute.tsx b/src/chat/components/ChatShellRoute.tsx new file mode 100644 index 000000000..2b6597016 --- /dev/null +++ b/src/chat/components/ChatShellRoute.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { createClientOnlyFn } from '@tanstack/react-start' +import { ClientOnly } from '@tanstack/react-router' +import { WorkspaceSkeleton } from './WorkspaceSkeleton' + +const LazyChatShell = React.lazy( + createClientOnlyFn(() => + import('./ChatShell').then((module) => ({ default: module.ChatShell })), + ), +) + +export function ChatShellRoute() { + return ( + }> + }> + + + + ) +} diff --git a/src/chat/components/projects/Loading.tsx b/src/chat/components/projects/Loading.tsx index 599fb6c18..0e292ff31 100644 --- a/src/chat/components/projects/Loading.tsx +++ b/src/chat/components/projects/Loading.tsx @@ -426,3 +426,16 @@ function SkeletonBlock({ className }: { className: string }) { /> ) } + +export function BuilderRouteContent({ + children, + pathname, +}: BuilderRouteFrameProps) { + return ( + + }> + {children} + + + ) +} diff --git a/src/chat/server/entries.ts b/src/chat/server/entries.ts new file mode 100644 index 000000000..9275c9460 --- /dev/null +++ b/src/chat/server/entries.ts @@ -0,0 +1,487 @@ +import { DurableObject, WorkflowEntrypoint } from 'cloudflare:workers' +import type { WorkflowEvent, WorkflowStep } from 'cloudflare:workers' +import type { + WorkflowDriverEnvironment, + WorkflowDriverParams, +} from './workflow-driver' +import type { Conversation as ConversationImplementation } from './conversation' +import type { WorkspaceSync as WorkspaceSyncImplementation } from './workspace-sync-object' +import type { StreamObject as StreamObjectImplementation } from '@durable-streams/server-cloudflare' + +// Keep the deployed class names and storage namespaces, but initialize chat only +// when Cloudflare creates one of its objects. RPC methods stay on the prototype. +export class Conversation extends DurableObject< + ConstructorParameters[1] +> { + private implementation!: ConversationImplementation + constructor( + ctx: ConstructorParameters[0], + env: ConstructorParameters[1], + ) { + super(ctx, env) + ctx.blockConcurrencyWhile(async () => { + const { Conversation } = await import('./conversation') + this.implementation = new Conversation(ctx, env) + }) + } + async alarm(...args: Parameters) { + return this.implementation.alarm(...args) + } + async copyBoundary( + ...args: Parameters + ) { + return this.implementation.copyBoundary(...args) + } + async captureThreadSource( + ...args: Parameters + ) { + return this.implementation.captureThreadSource(...args) + } + async captureRetrySourceJson( + ...args: Parameters + ) { + return this.implementation.captureRetrySourceJson(...args) + } + async captureRetrySource( + ...args: Parameters + ) { + return this.implementation.captureRetrySource(...args) + } + async actionEvidenceJson( + ...args: Parameters + ) { + return this.implementation.actionEvidenceJson(...args) + } + async retryPreparationSnapshot( + ...args: Parameters + ) { + return this.implementation.retryPreparationSnapshot(...args) + } + async startCopyExport( + ...args: Parameters + ) { + return this.implementation.startCopyExport(...args) + } + async readCopyPage( + ...args: Parameters + ) { + return this.implementation.readCopyPage(...args) + } + async readCopyPages( + ...args: Parameters + ) { + return this.implementation.readCopyPages(...args) + } + async importCopyPage( + ...args: Parameters + ) { + return this.implementation.importCopyPage(...args) + } + async importCopyPages( + ...args: Parameters + ) { + return this.implementation.importCopyPages(...args) + } + async finishCopyImport( + ...args: Parameters + ) { + return this.implementation.finishCopyImport(...args) + } + async activateCopy( + ...args: Parameters + ) { + return this.implementation.activateCopy(...args) + } + async discardCopyImport( + ...args: Parameters + ) { + return this.implementation.discardCopyImport(...args) + } + async releaseCopyExport( + ...args: Parameters + ) { + return this.implementation.releaseCopyExport(...args) + } + async wakeCopies( + ...args: Parameters + ) { + return this.implementation.wakeCopies(...args) + } + async bindIdentity( + ...args: Parameters + ) { + return this.implementation.bindIdentity(...args) + } + async lifecycleState( + ...args: Parameters + ) { + return this.implementation.lifecycleState(...args) + } + async reserveEmptyThread( + ...args: Parameters + ) { + return this.implementation.reserveEmptyThread(...args) + } + async reserveDeletion( + ...args: Parameters + ) { + return this.implementation.reserveDeletion(...args) + } + async releaseDeletion( + ...args: Parameters + ) { + return this.implementation.releaseDeletion(...args) + } + async streamSnapshot( + ...args: Parameters + ) { + return this.implementation.streamSnapshot(...args) + } + async referenceContext( + ...args: Parameters + ) { + return this.implementation.referenceContext(...args) + } + async archivedMessage( + ...args: Parameters + ) { + return this.implementation.archivedMessage(...args) + } + async archivedHistory( + ...args: Parameters + ) { + return this.implementation.archivedHistory(...args) + } + async transcriptNavigation( + ...args: Parameters + ) { + return this.implementation.transcriptNavigation(...args) + } + async readStream( + ...args: Parameters + ) { + return this.implementation.readStream(...args) + } + async snapshot(...args: Parameters) { + return this.implementation.snapshot(...args) + } + async executionSnapshot( + ...args: Parameters + ) { + return this.implementation.executionSnapshot(...args) + } + async executionHistory( + ...args: Parameters + ) { + return this.implementation.executionHistory(...args) + } + async executionEvents( + ...args: Parameters + ) { + return this.implementation.executionEvents(...args) + } + async changeExecution( + ...args: Parameters + ) { + return this.implementation.changeExecution(...args) + } + async executionProjectSnapshot( + ...args: Parameters + ) { + return this.implementation.executionProjectSnapshot(...args) + } + async reserveExecutionSnapshot( + ...args: Parameters + ) { + return this.implementation.reserveExecutionSnapshot(...args) + } + async publishExecutionSnapshot( + ...args: Parameters + ) { + return this.implementation.publishExecutionSnapshot(...args) + } + async prepareDelegation( + ...args: Parameters + ) { + return this.implementation.prepareDelegation(...args) + } + async listWorkflowRuns( + ...args: Parameters + ) { + return this.implementation.listWorkflowRuns(...args) + } + async readWorkflowAnswer( + ...args: Parameters + ) { + return this.implementation.readWorkflowAnswer(...args) + } + async readWorkflowOutputFile( + ...args: Parameters + ) { + return this.implementation.readWorkflowOutputFile(...args) + } + async readWorkflowFiles( + ...args: Parameters + ) { + return this.implementation.readWorkflowFiles(...args) + } + async inspectWorkflowRun( + ...args: Parameters + ) { + return this.implementation.inspectWorkflowRun(...args) + } + async launchWorkflowRun( + ...args: Parameters + ) { + return this.implementation.launchWorkflowRun(...args) + } + async withdrawWorkflowStart( + ...args: Parameters + ) { + return this.implementation.withdrawWorkflowStart(...args) + } + async startWorkflowRun( + ...args: Parameters + ) { + return this.implementation.startWorkflowRun(...args) + } + async advanceWorkflowRun( + ...args: Parameters + ) { + return this.implementation.advanceWorkflowRun(...args) + } + async cancelWorkflowRun( + ...args: Parameters + ) { + return this.implementation.cancelWorkflowRun(...args) + } + async settleWorkflowStep( + ...args: Parameters + ) { + return this.implementation.settleWorkflowStep(...args) + } + async workflowGrant( + ...args: Parameters + ) { + return this.implementation.workflowGrant(...args) + } + async workflowInputs( + ...args: Parameters + ) { + return this.implementation.workflowInputs(...args) + } + async workflowFileInput( + ...args: Parameters + ) { + return this.implementation.workflowFileInput(...args) + } + async reserveWorkflowOperation( + ...args: Parameters + ) { + return this.implementation.reserveWorkflowOperation(...args) + } + async delegationGrant( + ...args: Parameters + ) { + return this.implementation.delegationGrant(...args) + } + async reserveDelegationOperation( + ...args: Parameters< + ConversationImplementation['reserveDelegationOperation'] + > + ) { + return this.implementation.reserveDelegationOperation(...args) + } + async admitWorkflowRun( + ...args: Parameters + ) { + return this.implementation.admitWorkflowRun(...args) + } + async admitDelegatedRun( + ...args: Parameters + ) { + return this.implementation.admitDelegatedRun(...args) + } + async revokeDelegatedRun( + ...args: Parameters + ) { + return this.implementation.revokeDelegatedRun(...args) + } + async delegatedRunReport( + ...args: Parameters + ) { + return this.implementation.delegatedRunReport(...args) + } + async revokeWorkflowRun( + ...args: Parameters + ) { + return this.implementation.revokeWorkflowRun(...args) + } + async workflowStepUsage( + ...args: Parameters + ) { + return this.implementation.workflowStepUsage(...args) + } + async workflowRunResult( + ...args: Parameters + ) { + return this.implementation.workflowRunResult(...args) + } + async delegationSnapshot( + ...args: Parameters + ) { + return this.implementation.delegationSnapshot(...args) + } + async delegationHistory( + ...args: Parameters + ) { + return this.implementation.delegationHistory(...args) + } + async taskDelegations( + ...args: Parameters + ) { + return this.implementation.taskDelegations(...args) + } + async taskUsage( + ...args: Parameters + ) { + return this.implementation.taskUsage(...args) + } + async historicalTaskUsage( + ...args: Parameters + ) { + return this.implementation.historicalTaskUsage(...args) + } + async cancelDelegatedTask( + ...args: Parameters + ) { + return this.implementation.cancelDelegatedTask(...args) + } + async waitForDelegatedTasks( + ...args: Parameters + ) { + return this.implementation.waitForDelegatedTasks(...args) + } + async scheduleSnapshot( + ...args: Parameters + ) { + return this.implementation.scheduleSnapshot(...args) + } + async changeSchedule( + ...args: Parameters + ) { + return this.implementation.changeSchedule(...args) + } + async runHistory( + ...args: Parameters + ) { + return this.implementation.runHistory(...args) + } + async stop(...args: Parameters) { + return this.implementation.stop(...args) + } + async dismissPendingTask( + ...args: Parameters + ) { + return this.implementation.dismissPendingTask(...args) + } + async reset(...args: Parameters) { + return this.implementation.reset(...args) + } + async begin(...args: Parameters) { + return this.implementation.begin(...args) + } + async sendReceipt( + ...args: Parameters + ) { + return this.implementation.sendReceipt(...args) + } + async updateQueue( + ...args: Parameters + ) { + return this.implementation.updateQueue(...args) + } + async decideApproval( + ...args: Parameters + ) { + return this.implementation.decideApproval(...args) + } + async continueTask( + ...args: Parameters + ) { + return this.implementation.continueTask(...args) + } +} +export class WorkspaceSync extends DurableObject< + ConstructorParameters[1] +> { + private implementation!: WorkspaceSyncImplementation + constructor( + ctx: ConstructorParameters[0], + env: ConstructorParameters[1], + ) { + super(ctx, env) + ctx.blockConcurrencyWhile(async () => { + const { WorkspaceSync } = await import('./workspace-sync-object') + this.implementation = new WorkspaceSync(ctx, env) + }) + } + async snapshot(...args: Parameters) { + return this.implementation.snapshot(...args) + } + async publish(...args: Parameters) { + return this.implementation.publish(...args) + } + async read(...args: Parameters) { + return this.implementation.read(...args) + } + async alarm(...args: Parameters) { + return this.implementation.alarm(...args) + } +} +export class StreamObject extends DurableObject< + ConstructorParameters[1] +> { + private implementation!: StreamObjectImplementation + constructor( + ctx: ConstructorParameters[0], + env: ConstructorParameters[1], + ) { + super(ctx, env) + ctx.blockConcurrencyWhile(async () => { + const { StreamObject } = + await import('@durable-streams/server-cloudflare') + this.implementation = new StreamObject(ctx, env) + }) + } + async fetch(...args: Parameters) { + return this.implementation.fetch(...args) + } + async alarm(...args: Parameters) { + return this.implementation.alarm(...args) + } + async forkAcquire( + ...args: Parameters + ) { + return this.implementation.forkAcquire(...args) + } + async forkRelease( + ...args: Parameters + ) { + return this.implementation.forkRelease(...args) + } + async readRange( + ...args: Parameters + ) { + return this.implementation.readRange(...args) + } +} +export class TanChatWorkflow extends WorkflowEntrypoint< + WorkflowDriverEnvironment, + WorkflowDriverParams +> { + async run(event: WorkflowEvent, step: WorkflowStep) { + const { TanChatWorkflow } = await import('./workflow-driver') + return new TanChatWorkflow(this.ctx, this.env).run(event, step) + } +} diff --git a/src/chat/server/http/api/chat/account/composer-drafts/$accountId.$scope.ts b/src/chat/server/http/api/chat/account/composer-drafts/$accountId.$scope.ts new file mode 100644 index 000000000..d392a0ccd --- /dev/null +++ b/src/chat/server/http/api/chat/account/composer-drafts/$accountId.$scope.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/account/composer-drafts/$accountId/$scope', + z.object({ accountId: z.string(), scope: z.string() }), + { + GET: async ({ request, params }) => { + const { handleComposerDraft } = + await import('~/chat/server/composer-draft-http.server') + return handleComposerDraft(request, params.accountId, params.scope) + }, + POST: async ({ request, params }) => { + const { handleComposerDraft } = + await import('~/chat/server/composer-draft-http.server') + return handleComposerDraft(request, params.accountId, params.scope) + }, + }, +) diff --git a/src/chat/server/http/api/chat/account/devices.ts b/src/chat/server/http/api/chat/account/devices.ts new file mode 100644 index 000000000..dad4f864d --- /dev/null +++ b/src/chat/server/http/api/chat/account/devices.ts @@ -0,0 +1,15 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/account/devices', z.object({}), { + GET: async ({ request }) => { + const { handleDeviceAccount } = + await import('~/chat/server/device-http.server') + return handleDeviceAccount(request) + }, + POST: async ({ request }) => { + const { handleDeviceAccount } = + await import('~/chat/server/device-http.server') + return handleDeviceAccount(request) + }, +}) diff --git a/src/chat/server/http/api/chat/account/onboarding.ts b/src/chat/server/http/api/chat/account/onboarding.ts new file mode 100644 index 000000000..ef3604ec4 --- /dev/null +++ b/src/chat/server/http/api/chat/account/onboarding.ts @@ -0,0 +1,15 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/account/onboarding', z.object({}), { + GET: async ({ request }) => { + const { handleOnboarding } = + await import('~/chat/server/onboarding-http.server') + return handleOnboarding(request) + }, + POST: async ({ request }) => { + const { handleOnboarding } = + await import('~/chat/server/onboarding-http.server') + return handleOnboarding(request) + }, +}) diff --git a/src/chat/server/http/api/chat/account/preferences.ts b/src/chat/server/http/api/chat/account/preferences.ts new file mode 100644 index 000000000..1b7093bd4 --- /dev/null +++ b/src/chat/server/http/api/chat/account/preferences.ts @@ -0,0 +1,15 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/account/preferences', z.object({}), { + GET: async ({ request }) => { + const { handleAccountPreferences } = + await import('~/chat/server/account-preferences-http.server') + return handleAccountPreferences(request) + }, + POST: async ({ request }) => { + const { handleAccountPreferences } = + await import('~/chat/server/account-preferences-http.server') + return handleAccountPreferences(request) + }, +}) diff --git a/src/chat/server/http/api/chat/activity.ts b/src/chat/server/http/api/chat/activity.ts new file mode 100644 index 000000000..1bc1e0a09 --- /dev/null +++ b/src/chat/server/http/api/chat/activity.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/activity', z.object({}), { + GET: async ({ request }) => { + const { handleWorkspaceActivity } = + await import('~/chat/server/workspace-activity-http.server') + return handleWorkspaceActivity(request) + }, +}) diff --git a/src/chat/server/http/api/chat/app-version.ts b/src/chat/server/http/api/chat/app-version.ts new file mode 100644 index 000000000..1e48e9172 --- /dev/null +++ b/src/chat/server/http/api/chat/app-version.ts @@ -0,0 +1,12 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/app-version', z.object({}), { + GET: async () => { + const { appBuildId } = await import('~/chat/core/app-version') + return Response.json( + { build: appBuildId }, + { headers: { 'Cache-Control': 'no-store' } }, + ) + }, +}) diff --git a/src/chat/server/http/api/chat/bootstrap.ts b/src/chat/server/http/api/chat/bootstrap.ts new file mode 100644 index 000000000..e48450633 --- /dev/null +++ b/src/chat/server/http/api/chat/bootstrap.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/bootstrap', z.object({}), { + GET: async ({ request }) => { + const { handleBootstrap } = + await import('~/chat/server/bootstrap-http.server') + return handleBootstrap(request) + }, +}) diff --git a/src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.content.ts b/src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.content.ts new file mode 100644 index 000000000..5cfe1fc9d --- /dev/null +++ b/src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.content.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bot-drafts/$id/files/$fileId/content', + z.object({ id: z.string(), fileId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id, params.fileId, true) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.ts b/src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.ts new file mode 100644 index 000000000..50371b16b --- /dev/null +++ b/src/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bot-drafts/$id/files/$fileId', + z.object({ id: z.string(), fileId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id, params.fileId) + }, + POST: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id, params.fileId) + }, + PUT: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id, params.fileId) + }, + DELETE: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id, params.fileId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bot-drafts/$id.files.ts b/src/chat/server/http/api/chat/bot-drafts/$id.files.ts new file mode 100644 index 000000000..58699363e --- /dev/null +++ b/src/chat/server/http/api/chat/bot-drafts/$id.files.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bot-drafts/$id/files', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id) + }, + POST: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id) + }, + PUT: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id) + }, + DELETE: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bot-drafts', params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bot-drafts/$id.ts b/src/chat/server/http/api/chat/bot-drafts/$id.ts new file mode 100644 index 000000000..6a0a79793 --- /dev/null +++ b/src/chat/server/http/api/chat/bot-drafts/$id.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bot-drafts/$id', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleBotDraft } = + await import('~/chat/server/bot-draft-http.server') + return handleBotDraft(request, params.id) + }, + POST: async ({ request, params }) => { + const { handleBotDraft } = + await import('~/chat/server/bot-draft-http.server') + return handleBotDraft(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bots.ts b/src/chat/server/http/api/chat/bots.ts new file mode 100644 index 000000000..1a55c9ccf --- /dev/null +++ b/src/chat/server/http/api/chat/bots.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/bots', z.object({}), { + POST: async ({ request }) => { + const { handleBotMutation } = + await import('~/chat/server/bot-mutation-http.server') + return handleBotMutation(request) + }, +}) diff --git a/src/chat/server/http/api/chat/bots/$id.$operation.ts b/src/chat/server/http/api/chat/bots/$id.$operation.ts new file mode 100644 index 000000000..6c93adc69 --- /dev/null +++ b/src/chat/server/http/api/chat/bots/$id.$operation.ts @@ -0,0 +1,36 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bots/$id/$operation', + z.object({ id: z.string(), operation: z.string() }), + { + POST: async ({ request, params }) => { + const { handleConversationCopy } = + await import('~/chat/server/conversation-copy-http.server') + const { handleConversationRead, handleConversationSend } = + await import('~/chat/server/conversation-http.server') + const { handleBotMutation } = + await import('~/chat/server/bot-mutation-http.server') + return params.operation === 'copies' + ? handleConversationCopy(request, params.id, 'bot') + : ['move', 'delete', 'restore'].includes(params.operation) + ? handleBotMutation(request, params.id, params.operation) + : handleConversationSend(request, params.id, params.operation, 'bot') + }, + PATCH: async ({ request, params }) => { + const { handleBotMutation } = + await import('~/chat/server/bot-mutation-http.server') + return handleBotMutation(request, params.id, params.operation) + }, + GET: async ({ request, params }) => { + const { handleConversationCopy } = + await import('~/chat/server/conversation-copy-http.server') + const { handleConversationRead, handleConversationSend } = + await import('~/chat/server/conversation-http.server') + return params.operation === 'copy-source' + ? handleConversationCopy(request, params.id, 'bot', true) + : handleConversationRead(request, params.id, params.operation, 'bot') + }, + }, +) diff --git a/src/chat/server/http/api/chat/bots/$id.files.$fileId.content.ts b/src/chat/server/http/api/chat/bots/$id.files.$fileId.content.ts new file mode 100644 index 000000000..876340572 --- /dev/null +++ b/src/chat/server/http/api/chat/bots/$id.files.$fileId.content.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bots/$id/files/$fileId/content', + z.object({ id: z.string(), fileId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id, params.fileId, true) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bots/$id.files.$fileId.ts b/src/chat/server/http/api/chat/bots/$id.files.$fileId.ts new file mode 100644 index 000000000..60cd68f7c --- /dev/null +++ b/src/chat/server/http/api/chat/bots/$id.files.$fileId.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bots/$id/files/$fileId', + z.object({ id: z.string(), fileId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id, params.fileId) + }, + POST: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id, params.fileId) + }, + PUT: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id, params.fileId) + }, + DELETE: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id, params.fileId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bots/$id.files.ts b/src/chat/server/http/api/chat/bots/$id.files.ts new file mode 100644 index 000000000..afc646c59 --- /dev/null +++ b/src/chat/server/http/api/chat/bots/$id.files.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bots/$id/files', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id) + }, + POST: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id) + }, + PUT: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id) + }, + DELETE: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'bots', params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bots/$id.ts b/src/chat/server/http/api/chat/bots/$id.ts new file mode 100644 index 000000000..7fd5db52d --- /dev/null +++ b/src/chat/server/http/api/chat/bots/$id.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/bots/$id', + z.object({ id: z.string() }), + { + PATCH: async ({ request, params }) => { + const { handleBotMutation } = + await import('~/chat/server/bot-mutation-http.server') + return handleBotMutation(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/bots/bulk.ts b/src/chat/server/http/api/chat/bots/bulk.ts new file mode 100644 index 000000000..9707666bc --- /dev/null +++ b/src/chat/server/http/api/chat/bots/bulk.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/bots/bulk', z.object({}), { + POST: async ({ request }) => { + const { handleBotMutation } = + await import('~/chat/server/bot-mutation-http.server') + return handleBotMutation(request, undefined, undefined, 'bulk') + }, +}) diff --git a/src/chat/server/http/api/chat/bots/move.ts b/src/chat/server/http/api/chat/bots/move.ts new file mode 100644 index 000000000..fe12c92c6 --- /dev/null +++ b/src/chat/server/http/api/chat/bots/move.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/bots/move', z.object({}), { + POST: async ({ request }) => { + const { handleBotMutation } = + await import('~/chat/server/bot-mutation-http.server') + return handleBotMutation(request, undefined, undefined, 'move') + }, +}) diff --git a/src/chat/server/http/api/chat/connection.ts b/src/chat/server/http/api/chat/connection.ts new file mode 100644 index 000000000..bba43b097 --- /dev/null +++ b/src/chat/server/http/api/chat/connection.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/connection', z.object({}), { + POST: async ({ request }) => { + const { handleProviderConnection } = + await import('~/chat/server/provider-connection-http.server') + return handleProviderConnection(request) + }, +}) diff --git a/src/chat/server/http/api/chat/conversations/$id.$operation.ts b/src/chat/server/http/api/chat/conversations/$id.$operation.ts new file mode 100644 index 000000000..3d2023ddf --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.$operation.ts @@ -0,0 +1,76 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/$operation', + z.object({ id: z.string(), operation: z.string() }), + { + POST: async ({ request, params }) => { + const { handleConversationRetry } = + await import('~/chat/server/conversation-retry-http.server') + const { handleConversationCopy } = + await import('~/chat/server/conversation-copy-http.server') + const { handleWorkflowRun } = + await import('~/chat/server/workflow-run-http.server') + const { handleWorkflow } = + await import('~/chat/server/workflow-http.server') + const { handleConversationExecution } = + await import('~/chat/server/conversation-execution-http.server') + const { handleConversationSend } = + await import('~/chat/server/conversation-http.server') + const { handleConversationThreads } = + await import('~/chat/server/conversation-threads-http.server') + return params.operation === 'retries' + ? handleConversationRetry(request, params.id, 'create') + : params.operation === 'copies' + ? handleConversationCopy(request, params.id, 'conversation') + : params.operation === 'workflow-runs' + ? handleWorkflowRun(request, params.id) + : params.operation === 'workflows' + ? handleWorkflow(request, params.id) + : params.operation === 'execution' + ? handleConversationExecution(request, params.id) + : ['thread', 'threads'].includes(params.operation) + ? handleConversationThreads( + request, + params.id, + params.operation, + ) + : handleConversationSend(request, params.id, params.operation) + }, + DELETE: async ({ request, params }) => { + const { handleConversationThreads } = + await import('~/chat/server/conversation-threads-http.server') + return handleConversationThreads(request, params.id, params.operation) + }, + GET: async ({ request, params }) => { + const { handleConversationCopy } = + await import('~/chat/server/conversation-copy-http.server') + const { handleWorkflowRun } = + await import('~/chat/server/workflow-run-http.server') + const { handleWorkflow } = + await import('~/chat/server/workflow-http.server') + const { handleConversationExecution } = + await import('~/chat/server/conversation-execution-http.server') + const { handleConversationRead } = + await import('~/chat/server/conversation-http.server') + const { handleConversationThreads } = + await import('~/chat/server/conversation-threads-http.server') + return params.operation === 'copy-source' + ? handleConversationCopy(request, params.id, 'conversation', true) + : params.operation === 'workflow-runs' + ? handleWorkflowRun(request, params.id) + : params.operation === 'workflows' + ? handleWorkflow(request, params.id) + : params.operation === 'execution' + ? handleConversationExecution(request, params.id) + : ['thread', 'threads'].includes(params.operation) + ? handleConversationThreads( + request, + params.id, + params.operation, + ) + : handleConversationRead(request, params.id, params.operation) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts b/src/chat/server/http/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts new file mode 100644 index 000000000..ddec23f55 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/execution/snapshots/$snapshotId', + z.object({ id: z.string(), snapshotId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleConversationExecution } = + await import('~/chat/server/conversation-execution-http.server') + return handleConversationExecution(request, params.id, params.snapshotId) + }, + PUT: async ({ request, params }) => { + const { handleConversationExecution } = + await import('~/chat/server/conversation-execution-http.server') + return handleConversationExecution(request, params.id, params.snapshotId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id.files.$fileId.content.ts b/src/chat/server/http/api/chat/conversations/$id.files.$fileId.content.ts new file mode 100644 index 000000000..d2d17b55b --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.files.$fileId.content.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/files/$fileId/content', + z.object({ id: z.string(), fileId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles( + request, + 'conversations', + params.id, + params.fileId, + true, + ) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id.files.$fileId.ts b/src/chat/server/http/api/chat/conversations/$id.files.$fileId.ts new file mode 100644 index 000000000..c475ca668 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.files.$fileId.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/files/$fileId', + z.object({ id: z.string(), fileId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id, params.fileId) + }, + POST: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id, params.fileId) + }, + PUT: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id, params.fileId) + }, + DELETE: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id, params.fileId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id.files.ts b/src/chat/server/http/api/chat/conversations/$id.files.ts new file mode 100644 index 000000000..aca249c62 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.files.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/files', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id) + }, + POST: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id) + }, + PUT: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id) + }, + DELETE: async ({ request, params }) => { + const { handleFiles } = await import('~/chat/server/file-http.server') + return handleFiles(request, 'conversations', params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts b/src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts new file mode 100644 index 000000000..84bff5079 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/workflow-runs/$runId/cancel', + z.object({ id: z.string(), runId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleWorkflowRun } = + await import('~/chat/server/workflow-run-http.server') + return handleWorkflowRun(request, params.id, params.runId, true) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.ts b/src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.ts new file mode 100644 index 000000000..2f5156c6b --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/workflow-runs/$runId', + z.object({ id: z.string(), runId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleWorkflowRun } = + await import('~/chat/server/workflow-run-http.server') + return handleWorkflowRun(request, params.id, params.runId) + }, + POST: async ({ request, params }) => { + const { handleWorkflowRun } = + await import('~/chat/server/workflow-run-http.server') + return handleWorkflowRun(request, params.id, params.runId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id.workflows.$workflowId.ts b/src/chat/server/http/api/chat/conversations/$id.workflows.$workflowId.ts new file mode 100644 index 000000000..095de1225 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id.workflows.$workflowId.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/workflows/$workflowId', + z.object({ id: z.string(), workflowId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleWorkflow } = + await import('~/chat/server/workflow-http.server') + return handleWorkflow(request, params.id, params.workflowId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id/files/import.ts b/src/chat/server/http/api/chat/conversations/$id/files/import.ts new file mode 100644 index 000000000..0eacc17fc --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id/files/import.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/files/import', + z.object({ id: z.string() }), + { + POST: async ({ request, params }) => { + const { handleFileImport } = + await import('~/chat/server/file-http.server') + return handleFileImport(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id/memories.ts b/src/chat/server/http/api/chat/conversations/$id/memories.ts new file mode 100644 index 000000000..be02e94a0 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id/memories.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/memories', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleMemory } = await import('~/chat/server/memory-http.server') + return handleMemory(request, params.id) + }, + POST: async ({ request, params }) => { + const { handleMemory } = await import('~/chat/server/memory-http.server') + return handleMemory(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id/memories/$memoryId.ts b/src/chat/server/http/api/chat/conversations/$id/memories/$memoryId.ts new file mode 100644 index 000000000..6902b2362 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id/memories/$memoryId.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/memories/$memoryId', + z.object({ id: z.string(), memoryId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleMemory } = await import('~/chat/server/memory-http.server') + return handleMemory(request, params.id, params.memoryId) + }, + POST: async ({ request, params }) => { + const { handleMemory } = await import('~/chat/server/memory-http.server') + return handleMemory(request, params.id, params.memoryId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/conversations/$id/references.ts b/src/chat/server/http/api/chat/conversations/$id/references.ts new file mode 100644 index 000000000..47f64cd29 --- /dev/null +++ b/src/chat/server/http/api/chat/conversations/$id/references.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/conversations/$id/references', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleReferences } = + await import('~/chat/server/reference-http.server') + return handleReferences(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/copies/$id.ts b/src/chat/server/http/api/chat/copies/$id.ts new file mode 100644 index 000000000..cd877d0b5 --- /dev/null +++ b/src/chat/server/http/api/chat/copies/$id.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/copies/$id', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleConversationCopy } = + await import('~/chat/server/conversation-copy-http.server') + return handleConversationCopy(request, params.id, 'operation') + }, + }, +) diff --git a/src/chat/server/http/api/chat/device-transport.ts b/src/chat/server/http/api/chat/device-transport.ts new file mode 100644 index 000000000..1830716d9 --- /dev/null +++ b/src/chat/server/http/api/chat/device-transport.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/device-transport', z.object({}), { + POST: async ({ request }) => { + const { handleDeviceTransport } = + await import('~/chat/server/device-http.server') + return handleDeviceTransport(request) + }, +}) diff --git a/src/chat/server/http/api/chat/draft.ts b/src/chat/server/http/api/chat/draft.ts new file mode 100644 index 000000000..a8017512d --- /dev/null +++ b/src/chat/server/http/api/chat/draft.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/draft', z.object({}), { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/draft') + return handlers.GET(context) + }, + POST: async (context) => { + const { handlers } = await import('~/chat/server/routes/draft') + return handlers.POST(context) + }, +}) diff --git a/src/chat/server/http/api/chat/files.ts b/src/chat/server/http/api/chat/files.ts new file mode 100644 index 000000000..3c95bab6b --- /dev/null +++ b/src/chat/server/http/api/chat/files.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/files', z.object({}), { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/files') + return handlers.GET(context) + }, + PUT: async (context) => { + const { handlers } = await import('~/chat/server/routes/files') + return handlers.PUT(context) + }, +}) diff --git a/src/chat/server/http/api/chat/kody/account.ts b/src/chat/server/http/api/chat/kody/account.ts new file mode 100644 index 000000000..2d7f625e5 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/account.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/account', z.object({}), { + GET: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'account') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/callback.ts b/src/chat/server/http/api/chat/kody/callback.ts new file mode 100644 index 000000000..365bfe836 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/callback.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/callback', z.object({}), { + GET: async ({ request }) => { + const { handleKodyCallback } = + await import('~/chat/server/kody-callback-http.server') + return handleKodyCallback(request) + }, +}) diff --git a/src/chat/server/http/api/chat/kody/community.ts b/src/chat/server/http/api/chat/kody/community.ts new file mode 100644 index 000000000..b5a97765a --- /dev/null +++ b/src/chat/server/http/api/chat/kody/community.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/community', z.object({}), { + GET: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'community') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/community/$listingId.ts b/src/chat/server/http/api/chat/kody/community/$listingId.ts new file mode 100644 index 000000000..e3f1566f5 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/community/$listingId.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/community/$listingId', + z.object({ listingId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'community', params.listingId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/connect.ts b/src/chat/server/http/api/chat/kody/connect.ts new file mode 100644 index 000000000..4616b63de --- /dev/null +++ b/src/chat/server/http/api/chat/kody/connect.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/connect', z.object({}), { + POST: async ({ request }) => { + const { handleKodyConnect } = + await import('~/chat/server/kody-connect-http.server') + return handleKodyConnect(request) + }, +}) diff --git a/src/chat/server/http/api/chat/kody/jobs/$jobId/enabled.ts b/src/chat/server/http/api/chat/kody/jobs/$jobId/enabled.ts new file mode 100644 index 000000000..75531af4d --- /dev/null +++ b/src/chat/server/http/api/chat/kody/jobs/$jobId/enabled.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/jobs/$jobId/enabled', + z.object({ jobId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleKodyControl } = + await import('~/chat/server/kody-control-http.server') + return handleKodyControl(request, 'job-enabled', params.jobId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/mail/inboxes.ts b/src/chat/server/http/api/chat/kody/mail/inboxes.ts new file mode 100644 index 000000000..029690b66 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/mail/inboxes.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/mail/inboxes', z.object({}), { + GET: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'mail-inboxes') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/mail/messages.ts b/src/chat/server/http/api/chat/kody/mail/messages.ts new file mode 100644 index 000000000..05d5ef9cf --- /dev/null +++ b/src/chat/server/http/api/chat/kody/mail/messages.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/mail/messages', z.object({}), { + GET: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'mail-messages') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/mail/messages/$messageId.ts b/src/chat/server/http/api/chat/kody/mail/messages/$messageId.ts new file mode 100644 index 000000000..bf54e1ab4 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/mail/messages/$messageId.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/mail/messages/$messageId', + z.object({ messageId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'mail-messages', params.messageId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/memories.ts b/src/chat/server/http/api/chat/kody/memories.ts new file mode 100644 index 000000000..46cdfbb08 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/memories.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/memories', z.object({}), { + GET: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'memories') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/memories/$memoryId.ts b/src/chat/server/http/api/chat/kody/memories/$memoryId.ts new file mode 100644 index 000000000..9451dd1b6 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/memories/$memoryId.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/memories/$memoryId', + z.object({ memoryId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'memories', params.memoryId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/memories/$memoryId/apply.ts b/src/chat/server/http/api/chat/kody/memories/$memoryId/apply.ts new file mode 100644 index 000000000..00e53252e --- /dev/null +++ b/src/chat/server/http/api/chat/kody/memories/$memoryId/apply.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/memories/$memoryId/apply', + z.object({ memoryId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleKodyMemoryMutation } = + await import('~/chat/server/kody-memory-http.server') + return handleKodyMemoryMutation(request, 'apply', params.memoryId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/memories/$memoryId/review.ts b/src/chat/server/http/api/chat/kody/memories/$memoryId/review.ts new file mode 100644 index 000000000..acaa93058 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/memories/$memoryId/review.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/memories/$memoryId/review', + z.object({ memoryId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleKodyMemoryMutation } = + await import('~/chat/server/kody-memory-http.server') + return handleKodyMemoryMutation(request, 'review', params.memoryId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/memories/create/apply.ts b/src/chat/server/http/api/chat/kody/memories/create/apply.ts new file mode 100644 index 000000000..68a6d65cf --- /dev/null +++ b/src/chat/server/http/api/chat/kody/memories/create/apply.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/memories/create/apply', + z.object({}), + { + POST: async ({ request }) => { + const { handleKodyMemoryMutation } = + await import('~/chat/server/kody-memory-http.server') + return handleKodyMemoryMutation(request, 'apply') + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/memories/create/review.ts b/src/chat/server/http/api/chat/kody/memories/create/review.ts new file mode 100644 index 000000000..339d0c4a1 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/memories/create/review.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/memories/create/review', + z.object({}), + { + POST: async ({ request }) => { + const { handleKodyMemoryMutation } = + await import('~/chat/server/kody-memory-http.server') + return handleKodyMemoryMutation(request, 'review') + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/packages/$packageId/document.ts b/src/chat/server/http/api/chat/kody/packages/$packageId/document.ts new file mode 100644 index 000000000..97e50fbab --- /dev/null +++ b/src/chat/server/http/api/chat/kody/packages/$packageId/document.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/packages/$packageId/document', + z.object({ packageId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'package-document', params.packageId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/resources/$kind.ts b/src/chat/server/http/api/chat/kody/resources/$kind.ts new file mode 100644 index 000000000..c887590ca --- /dev/null +++ b/src/chat/server/http/api/chat/kody/resources/$kind.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/resources/$kind', + z.object({ kind: z.string() }), + { + GET: async ({ request, params }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'resources', params.kind) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/runs.ts b/src/chat/server/http/api/chat/kody/runs.ts new file mode 100644 index 000000000..d4291380e --- /dev/null +++ b/src/chat/server/http/api/chat/kody/runs.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/runs', z.object({}), { + GET: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'runs') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/runs/$runId.ts b/src/chat/server/http/api/chat/kody/runs/$runId.ts new file mode 100644 index 000000000..036631c3a --- /dev/null +++ b/src/chat/server/http/api/chat/kody/runs/$runId.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/runs/$runId', + z.object({ runId: z.string() }), + { + GET: async ({ request, params }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'runs', params.runId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/runs/$runId/triage.ts b/src/chat/server/http/api/chat/kody/runs/$runId/triage.ts new file mode 100644 index 000000000..cd7ddbb9b --- /dev/null +++ b/src/chat/server/http/api/chat/kody/runs/$runId/triage.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/runs/$runId/triage', + z.object({ runId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleKodyControl } = + await import('~/chat/server/kody-control-http.server') + return handleKodyControl(request, 'run-triage', params.runId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/servers.ts b/src/chat/server/http/api/chat/kody/servers.ts new file mode 100644 index 000000000..fab8c3b83 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/servers.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/servers', z.object({}), { + POST: async ({ request }) => { + const { handleKodyControl } = + await import('~/chat/server/kody-control-http.server') + return handleKodyControl(request, 'server-add') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/servers/$serverId/check.ts b/src/chat/server/http/api/chat/kody/servers/$serverId/check.ts new file mode 100644 index 000000000..87a7a3a11 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/servers/$serverId/check.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/servers/$serverId/check', + z.object({ serverId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleKodyControl } = + await import('~/chat/server/kody-control-http.server') + return handleKodyControl(request, 'server-check', params.serverId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/servers/$serverId/enabled.ts b/src/chat/server/http/api/chat/kody/servers/$serverId/enabled.ts new file mode 100644 index 000000000..a0aa45371 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/servers/$serverId/enabled.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/servers/$serverId/enabled', + z.object({ serverId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleKodyControl } = + await import('~/chat/server/kody-control-http.server') + return handleKodyControl(request, 'server-enabled', params.serverId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/servers/$serverId/reconnect.ts b/src/chat/server/http/api/chat/kody/servers/$serverId/reconnect.ts new file mode 100644 index 000000000..3db15a2de --- /dev/null +++ b/src/chat/server/http/api/chat/kody/servers/$serverId/reconnect.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/kody/servers/$serverId/reconnect', + z.object({ serverId: z.string() }), + { + POST: async ({ request, params }) => { + const { handleKodyControl } = + await import('~/chat/server/kody-control-http.server') + return handleKodyControl(request, 'server-reconnect', params.serverId) + }, + }, +) diff --git a/src/chat/server/http/api/chat/kody/sync.ts b/src/chat/server/http/api/chat/kody/sync.ts new file mode 100644 index 000000000..383c5dda7 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/sync.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/sync', z.object({}), { + POST: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'sync') + }, +}) diff --git a/src/chat/server/http/api/chat/kody/unlink.ts b/src/chat/server/http/api/chat/kody/unlink.ts new file mode 100644 index 000000000..cfec8a12c --- /dev/null +++ b/src/chat/server/http/api/chat/kody/unlink.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/unlink', z.object({}), { + POST: async ({ request }) => { + const { handleKodyUnlink } = + await import('~/chat/server/kody-unlink-http.server') + return handleKodyUnlink(request) + }, +}) diff --git a/src/chat/server/http/api/chat/kody/usage.ts b/src/chat/server/http/api/chat/kody/usage.ts new file mode 100644 index 000000000..98e825768 --- /dev/null +++ b/src/chat/server/http/api/chat/kody/usage.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/kody/usage', z.object({}), { + GET: async ({ request }) => { + const { handleKodyAccount } = + await import('~/chat/server/kody-account-http.server') + return handleKodyAccount(request, 'usage') + }, +}) diff --git a/src/chat/server/http/api/chat/mcp/$.ts b/src/chat/server/http/api/chat/mcp/$.ts new file mode 100644 index 000000000..7b43177ab --- /dev/null +++ b/src/chat/server/http/api/chat/mcp/$.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/mcp/$', + z.object({ _splat: z.string() }), + { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/mcp/$') + return handlers.GET(context) + }, + POST: async (context) => { + const { handlers } = await import('~/chat/server/routes/mcp/$') + return handlers.POST(context) + }, + }, +) diff --git a/src/chat/server/http/api/chat/mcp/contract.ts b/src/chat/server/http/api/chat/mcp/contract.ts new file mode 100644 index 000000000..e7e4ab1b7 --- /dev/null +++ b/src/chat/server/http/api/chat/mcp/contract.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/mcp/contract', z.object({}), { + GET: async ({ request }) => { + const { handleReferenceCatalog } = + await import('~/chat/server/reference-catalog-http.server') + return handleReferenceCatalog(request, 'mcp-contract') + }, +}) diff --git a/src/chat/server/http/api/chat/model-options.ts b/src/chat/server/http/api/chat/model-options.ts new file mode 100644 index 000000000..a3705095a --- /dev/null +++ b/src/chat/server/http/api/chat/model-options.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/model-options', z.object({}), { + GET: async ({ request }) => { + const { handleModelOptions } = + await import('~/chat/server/model-options-http.server') + return handleModelOptions(request) + }, +}) diff --git a/src/chat/server/http/api/chat/models.ts b/src/chat/server/http/api/chat/models.ts new file mode 100644 index 000000000..f440245e9 --- /dev/null +++ b/src/chat/server/http/api/chat/models.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/models', z.object({}), { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/models') + return handlers.GET(context) + }, +}) diff --git a/src/chat/server/http/api/chat/openrouter/callback.ts b/src/chat/server/http/api/chat/openrouter/callback.ts new file mode 100644 index 000000000..e68b87720 --- /dev/null +++ b/src/chat/server/http/api/chat/openrouter/callback.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/openrouter/callback', z.object({}), { + GET: async () => { + const { handlers } = + await import('~/chat/server/routes/openrouter/callback') + return handlers.GET() + }, +}) diff --git a/src/chat/server/http/api/chat/plugins.ts b/src/chat/server/http/api/chat/plugins.ts new file mode 100644 index 000000000..8136ce374 --- /dev/null +++ b/src/chat/server/http/api/chat/plugins.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/plugins', z.object({}), { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/plugins') + return handlers.GET(context) + }, + POST: async (context) => { + const { handlers } = await import('~/chat/server/routes/plugins') + return handlers.POST(context) + }, +}) diff --git a/src/chat/server/http/api/chat/plugins/$id.ts b/src/chat/server/http/api/chat/plugins/$id.ts new file mode 100644 index 000000000..ef8ac1b8c --- /dev/null +++ b/src/chat/server/http/api/chat/plugins/$id.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/plugins/$id', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handlePlugins } = await import('~/chat/server/routes/plugins') + return handlePlugins(request, params.id) + }, + POST: async ({ request, params }) => { + const { handlePlugins } = await import('~/chat/server/routes/plugins') + return handlePlugins(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/policy.ts b/src/chat/server/http/api/chat/policy.ts new file mode 100644 index 000000000..0d08125d5 --- /dev/null +++ b/src/chat/server/http/api/chat/policy.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/policy', z.object({}), { + POST: async ({ request }) => { + const { handleWorkspacePolicy } = + await import('~/chat/server/workspace-policy-http.server') + return handleWorkspacePolicy(request) + }, +}) diff --git a/src/chat/server/http/api/chat/project-snapshots.$hash.quarantine.ts b/src/chat/server/http/api/chat/project-snapshots.$hash.quarantine.ts new file mode 100644 index 000000000..fe0e7178e --- /dev/null +++ b/src/chat/server/http/api/chat/project-snapshots.$hash.quarantine.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/project-snapshots/$hash/quarantine', + z.object({ hash: z.string() }), + { + POST: async (context) => { + const { handlers } = + await import('~/chat/server/routes/project-snapshots.$hash.quarantine') + return handlers.POST({ + request: context.request, + params: { hash: context.params.hash }, + }) + }, + }, +) diff --git a/src/chat/server/http/api/chat/project-snapshots.$hash.ts b/src/chat/server/http/api/chat/project-snapshots.$hash.ts new file mode 100644 index 000000000..3b93a7d9a --- /dev/null +++ b/src/chat/server/http/api/chat/project-snapshots.$hash.ts @@ -0,0 +1,47 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/project-snapshots/$hash', + z.object({ hash: z.string() }), + { + GET: async ({ params }) => { + const { jsonError } = await import('~/utils/api-boundary.server') + const { + getBuilderProjectSnapshotCacheTag, + getBuilderProjectSnapshotObject, + isBuilderProjectSnapshotHash, + BuilderProjectSnapshotStorageUnavailableError, + } = await import('~/utils/builder-project-snapshot-storage.server') + + if (!isBuilderProjectSnapshotHash(params.hash)) { + return jsonError('Project snapshot not found', 404) + } + + try { + const object = await getBuilderProjectSnapshotObject(params.hash) + if (!object) return jsonError('Project snapshot not found', 404) + + const body = new Blob([await object.arrayBuffer()]) + .stream() + .pipeThrough(new DecompressionStream('gzip')) + + return new Response(body, { + headers: { + 'Cache-Control': 'public, max-age=300, must-revalidate', + 'Cache-Tag': getBuilderProjectSnapshotCacheTag(params.hash), + 'Cloudflare-CDN-Cache-Control': + 'public, max-age=31536000, immutable', + 'Content-Type': 'application/json; charset=utf-8', + ETag: `"${params.hash}"`, + }, + }) + } catch (error) { + if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { + return jsonError('Project snapshot storage is unavailable', 503) + } + throw error + } + }, + }, +) diff --git a/src/chat/server/http/api/chat/project-snapshots.ts b/src/chat/server/http/api/chat/project-snapshots.ts new file mode 100644 index 000000000..96cb5a89c --- /dev/null +++ b/src/chat/server/http/api/chat/project-snapshots.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/project-snapshots', z.object({}), { + POST: async (context) => { + const { handlers } = await import('~/chat/server/routes/project-snapshots') + return handlers.POST(context) + }, +}) diff --git a/src/chat/server/http/api/chat/projects.$id.sync.ts b/src/chat/server/http/api/chat/projects.$id.sync.ts new file mode 100644 index 000000000..eddd7e04e --- /dev/null +++ b/src/chat/server/http/api/chat/projects.$id.sync.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/projects/$id/sync', + z.object({ id: z.string() }), + { + GET: async (context) => { + const { handlers } = + await import('~/chat/server/routes/projects.$id.sync') + return handlers.GET({ + request: context.request, + params: { id: context.params.id }, + }) + }, + POST: async (context) => { + const { handlers } = + await import('~/chat/server/routes/projects.$id.sync') + return handlers.POST({ + request: context.request, + params: { id: context.params.id }, + }) + }, + }, +) diff --git a/src/chat/server/http/api/chat/projects.$id.ts b/src/chat/server/http/api/chat/projects.$id.ts new file mode 100644 index 000000000..1bbd14887 --- /dev/null +++ b/src/chat/server/http/api/chat/projects.$id.ts @@ -0,0 +1,27 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/projects/$id', + z.object({ id: z.string() }), + { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/projects.$id') + return handlers.GET({ params: { id: context.params.id } }) + }, + PATCH: async (context) => { + const { handlers } = await import('~/chat/server/routes/projects.$id') + return handlers.PATCH({ + request: context.request, + params: { id: context.params.id }, + }) + }, + DELETE: async (context) => { + const { handlers } = await import('~/chat/server/routes/projects.$id') + return handlers.DELETE({ + request: context.request, + params: { id: context.params.id }, + }) + }, + }, +) diff --git a/src/chat/server/http/api/chat/projects.ts b/src/chat/server/http/api/chat/projects.ts new file mode 100644 index 000000000..33eca3961 --- /dev/null +++ b/src/chat/server/http/api/chat/projects.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/projects', z.object({}), { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/projects') + return handlers.GET(context) + }, + POST: async (context) => { + const { handlers } = await import('~/chat/server/routes/projects') + return handlers.POST(context) + }, +}) diff --git a/src/chat/server/http/api/chat/recipes.ts b/src/chat/server/http/api/chat/recipes.ts new file mode 100644 index 000000000..7947c374f --- /dev/null +++ b/src/chat/server/http/api/chat/recipes.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/recipes', z.object({}), { + POST: async ({ request }) => { + const { handleSavedActions } = + await import('~/chat/server/saved-actions-http.server') + return handleSavedActions(request) + }, +}) diff --git a/src/chat/server/http/api/chat/recipes/$id.ts b/src/chat/server/http/api/chat/recipes/$id.ts new file mode 100644 index 000000000..611fd07a5 --- /dev/null +++ b/src/chat/server/http/api/chat/recipes/$id.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/recipes/$id', + z.object({ id: z.string() }), + { + DELETE: async ({ request, params }) => { + const { handleSavedActions } = + await import('~/chat/server/saved-actions-http.server') + return handleSavedActions(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/references.ts b/src/chat/server/http/api/chat/references.ts new file mode 100644 index 000000000..1d232e49a --- /dev/null +++ b/src/chat/server/http/api/chat/references.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/references', z.object({}), { + GET: async ({ request }) => { + const { handleReferences } = + await import('~/chat/server/reference-http.server') + return handleReferences(request) + }, +}) diff --git a/src/chat/server/http/api/chat/references/kody/inspect.ts b/src/chat/server/http/api/chat/references/kody/inspect.ts new file mode 100644 index 000000000..edd571b30 --- /dev/null +++ b/src/chat/server/http/api/chat/references/kody/inspect.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/references/kody/inspect', + z.object({}), + { + GET: async ({ request }) => { + const { handleReferenceCatalog } = + await import('~/chat/server/reference-catalog-http.server') + return handleReferenceCatalog(request, 'kody-inspect') + }, + }, +) diff --git a/src/chat/server/http/api/chat/references/kody/refresh.ts b/src/chat/server/http/api/chat/references/kody/refresh.ts new file mode 100644 index 000000000..fa76bece3 --- /dev/null +++ b/src/chat/server/http/api/chat/references/kody/refresh.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/references/kody/refresh', + z.object({}), + { + POST: async ({ request }) => { + const { handleReferenceCatalog } = + await import('~/chat/server/reference-catalog-http.server') + return handleReferenceCatalog(request, 'kody-refresh') + }, + }, +) diff --git a/src/chat/server/http/api/chat/references/tools/refresh.ts b/src/chat/server/http/api/chat/references/tools/refresh.ts new file mode 100644 index 000000000..0d8598f4a --- /dev/null +++ b/src/chat/server/http/api/chat/references/tools/refresh.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/references/tools/refresh', + z.object({}), + { + POST: async ({ request }) => { + const { handleReferenceCatalog } = + await import('~/chat/server/reference-catalog-http.server') + return handleReferenceCatalog(request, 'tools-refresh') + }, + }, +) diff --git a/src/chat/server/http/api/chat/retries/$id.prepare.ts b/src/chat/server/http/api/chat/retries/$id.prepare.ts new file mode 100644 index 000000000..174a36141 --- /dev/null +++ b/src/chat/server/http/api/chat/retries/$id.prepare.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/retries/$id/prepare', + z.object({ id: z.string() }), + { + POST: async ({ request, params }) => { + const { handleConversationRetry } = + await import('~/chat/server/conversation-retry-http.server') + return handleConversationRetry(request, params.id, 'prepare') + }, + }, +) diff --git a/src/chat/server/http/api/chat/retries/$id.ts b/src/chat/server/http/api/chat/retries/$id.ts new file mode 100644 index 000000000..7c18e8588 --- /dev/null +++ b/src/chat/server/http/api/chat/retries/$id.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/retries/$id', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleConversationRetry } = + await import('~/chat/server/conversation-retry-http.server') + return handleConversationRetry(request, params.id, 'get') + }, + }, +) diff --git a/src/chat/server/http/api/chat/sections.ts b/src/chat/server/http/api/chat/sections.ts new file mode 100644 index 000000000..285da9394 --- /dev/null +++ b/src/chat/server/http/api/chat/sections.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/sections', z.object({}), { + POST: async ({ request }) => { + const { handleWorkspaceSections } = + await import('~/chat/server/workspace-sections-http.server') + return handleWorkspaceSections(request) + }, +}) diff --git a/src/chat/server/http/api/chat/sections/$id.ts b/src/chat/server/http/api/chat/sections/$id.ts new file mode 100644 index 000000000..791c34ee8 --- /dev/null +++ b/src/chat/server/http/api/chat/sections/$id.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/sections/$id', + z.object({ id: z.string() }), + { + PATCH: async ({ request, params }) => { + const { handleWorkspaceSections } = + await import('~/chat/server/workspace-sections-http.server') + return handleWorkspaceSections(request, params.id) + }, + DELETE: async ({ request, params }) => { + const { handleWorkspaceSections } = + await import('~/chat/server/workspace-sections-http.server') + return handleWorkspaceSections(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/skills.ts b/src/chat/server/http/api/chat/skills.ts new file mode 100644 index 000000000..7e076f6d2 --- /dev/null +++ b/src/chat/server/http/api/chat/skills.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/skills', z.object({}), { + GET: async (context) => { + const { handlers } = await import('~/chat/server/routes/skills') + return handlers.GET(context) + }, + POST: async (context) => { + const { handlers } = await import('~/chat/server/routes/skills') + return handlers.POST(context) + }, +}) diff --git a/src/chat/server/http/api/chat/skills/$id.ts b/src/chat/server/http/api/chat/skills/$id.ts new file mode 100644 index 000000000..cde2d39d8 --- /dev/null +++ b/src/chat/server/http/api/chat/skills/$id.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/skills/$id', + z.object({ id: z.string() }), + { + GET: async ({ request, params }) => { + const { handleSkills } = await import('~/chat/server/routes/skills') + return handleSkills(request, params.id) + }, + POST: async ({ request, params }) => { + const { handleSkills } = await import('~/chat/server/routes/skills') + return handleSkills(request, params.id) + }, + }, +) diff --git a/src/chat/server/http/api/chat/workspace-history.ts b/src/chat/server/http/api/chat/workspace-history.ts new file mode 100644 index 000000000..95b023552 --- /dev/null +++ b/src/chat/server/http/api/chat/workspace-history.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/workspace-history', z.object({}), { + POST: async ({ request }) => { + const { handleBotMutation } = + await import('~/chat/server/bot-mutation-http.server') + return handleBotMutation(request, undefined, undefined, 'history') + }, +}) diff --git a/src/chat/server/http/api/chat/workspace-index.activity.ts b/src/chat/server/http/api/chat/workspace-index.activity.ts new file mode 100644 index 000000000..627dbba7a --- /dev/null +++ b/src/chat/server/http/api/chat/workspace-index.activity.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/workspace-index/activity', + z.object({}), + { + GET: async ({ request }) => { + const { handleWorkspaceActivity } = + await import('~/chat/server/workspace-activity-http.server') + return handleWorkspaceActivity(request, true) + }, + }, +) diff --git a/src/chat/server/http/api/chat/workspace-index.ts b/src/chat/server/http/api/chat/workspace-index.ts new file mode 100644 index 000000000..033d73f8f --- /dev/null +++ b/src/chat/server/http/api/chat/workspace-index.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/workspace-index', z.object({}), { + GET: async ({ request }) => { + const { handleWorkspaceIndex } = + await import('~/chat/server/workspace-index-http.server') + return handleWorkspaceIndex(request) + }, +}) diff --git a/src/chat/server/http/api/chat/workspace-sync/$operation.ts b/src/chat/server/http/api/chat/workspace-sync/$operation.ts new file mode 100644 index 000000000..66266512c --- /dev/null +++ b/src/chat/server/http/api/chat/workspace-sync/$operation.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute( + '/api/chat/workspace-sync/$operation', + z.object({ operation: z.string() }), + { + GET: async ({ request, params }) => { + const { handleWorkspaceSync } = + await import('~/chat/server/workspace-sync-http.server') + return handleWorkspaceSync(request, params.operation) + }, + }, +) diff --git a/src/chat/server/http/api/chat/workspace.ts b/src/chat/server/http/api/chat/workspace.ts new file mode 100644 index 000000000..c343844c4 --- /dev/null +++ b/src/chat/server/http/api/chat/workspace.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/api/chat/workspace', z.object({}), { + POST: async ({ request }) => { + const { hasChatAccess } = await import('~/chat/access.server') + const { getAuthService } = await import('~/auth/index.server') + const { openPersonalChatWorkspace } = + await import('~/chat/workspace.server') + const { jsonError, jsonResponse, validateSameOriginRequest } = + await import('~/utils/api-boundary.server') + + const boundaryError = validateSameOriginRequest(request) + if (boundaryError) + return jsonError(boundaryError.message, boundaryError.status) + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Sign in to TanStack.', 401) + if (!(await hasChatAccess(user))) + return jsonError('Chat access is not enabled for this account.', 403) + const workspace = await openPersonalChatWorkspace(user.userId) + return jsonResponse(workspace) + }, +}) diff --git a/src/chat/server/http/auth/mcp/callback.ts b/src/chat/server/http/auth/mcp/callback.ts new file mode 100644 index 000000000..e0722fdc0 --- /dev/null +++ b/src/chat/server/http/auth/mcp/callback.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/auth/mcp/callback', z.object({}), { + GET: async ({ request }) => { + const { handleMcpOAuth } = + await import('~/chat/server/mcp-oauth-route.server') + return handleMcpOAuth(request) + }, +}) diff --git a/src/chat/server/http/auth/mcp/client-metadata.ts b/src/chat/server/http/auth/mcp/client-metadata.ts new file mode 100644 index 000000000..bc33b471e --- /dev/null +++ b/src/chat/server/http/auth/mcp/client-metadata.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' +import { defineHttpRoute } from '~/server/runtime/http-route.server' + +export default defineHttpRoute('/auth/mcp/client-metadata', z.object({}), { + GET: async ({ request }) => { + const { handleMcpOAuth } = + await import('~/chat/server/mcp-oauth-route.server') + return handleMcpOAuth(request) + }, +}) diff --git a/src/chat/server/http/routes.ts b/src/chat/server/http/routes.ts new file mode 100644 index 000000000..3ef2cddbf --- /dev/null +++ b/src/chat/server/http/routes.ts @@ -0,0 +1,196 @@ +import route48 from '~/chat/server/http/api/chat/kody/memories/create/apply' +import route49 from '~/chat/server/http/api/chat/kody/memories/create/review' +import route43 from '~/chat/server/http/api/chat/kody/mail/messages/$messageId' +import route42 from '~/chat/server/http/api/chat/kody/mail/inboxes' +import route44 from '~/chat/server/http/api/chat/kody/mail/messages' +import route79 from '~/chat/server/http/api/chat/references/kody/inspect' +import route80 from '~/chat/server/http/api/chat/references/kody/refresh' +import route81 from '~/chat/server/http/api/chat/references/tools/refresh' +import route41 from '~/chat/server/http/api/chat/kody/jobs/$jobId/enabled' +import route45 from '~/chat/server/http/api/chat/kody/memories/$memoryId/apply' +import route46 from '~/chat/server/http/api/chat/kody/memories/$memoryId/review' +import route51 from '~/chat/server/http/api/chat/kody/packages/$packageId/document' +import route53 from '~/chat/server/http/api/chat/kody/runs/$runId/triage' +import route56 from '~/chat/server/http/api/chat/kody/servers/$serverId/check' +import route57 from '~/chat/server/http/api/chat/kody/servers/$serverId/enabled' +import route58 from '~/chat/server/http/api/chat/kody/servers/$serverId/reconnect' +import route0 from '~/chat/server/http/api/chat/account/composer-drafts/$accountId.$scope' +import route38 from '~/chat/server/http/api/chat/kody/community/$listingId' +import route47 from '~/chat/server/http/api/chat/kody/memories/$memoryId' +import route52 from '~/chat/server/http/api/chat/kody/resources/$kind' +import route54 from '~/chat/server/http/api/chat/kody/runs/$runId' +import route1 from '~/chat/server/http/api/chat/account/devices' +import route2 from '~/chat/server/http/api/chat/account/onboarding' +import route3 from '~/chat/server/http/api/chat/account/preferences' +import route16 from '~/chat/server/http/api/chat/bots/bulk' +import route17 from '~/chat/server/http/api/chat/bots/move' +import route36 from '~/chat/server/http/api/chat/kody/account' +import route37 from '~/chat/server/http/api/chat/kody/callback' +import route39 from '~/chat/server/http/api/chat/kody/community' +import route40 from '~/chat/server/http/api/chat/kody/connect' +import route50 from '~/chat/server/http/api/chat/kody/memories' +import route55 from '~/chat/server/http/api/chat/kody/runs' +import route59 from '~/chat/server/http/api/chat/kody/servers' +import route60 from '~/chat/server/http/api/chat/kody/sync' +import route61 from '~/chat/server/http/api/chat/kody/unlink' +import route62 from '~/chat/server/http/api/chat/kody/usage' +import route64 from '~/chat/server/http/api/chat/mcp/contract' +import route67 from '~/chat/server/http/api/chat/openrouter/callback' +import route90 from '~/chat/server/http/api/chat/workspace-index.activity' +import route25 from '~/chat/server/http/api/chat/conversations/$id.execution.snapshots.$snapshotId' +import route20 from '~/chat/server/http/api/chat/conversations/$id/files/import' +import route7 from '~/chat/server/http/api/chat/bot-drafts/$id.files.$fileId.content' +import route12 from '~/chat/server/http/api/chat/bots/$id.files.$fileId.content' +import route26 from '~/chat/server/http/api/chat/conversations/$id.files.$fileId.content' +import route29 from '~/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId.cancel' +import route8 from '~/chat/server/http/api/chat/bot-drafts/$id.files.$fileId' +import route13 from '~/chat/server/http/api/chat/bots/$id.files.$fileId' +import route27 from '~/chat/server/http/api/chat/conversations/$id.files.$fileId' +import route21 from '~/chat/server/http/api/chat/conversations/$id/memories/$memoryId' +import route30 from '~/chat/server/http/api/chat/conversations/$id.workflow-runs.$runId' +import route31 from '~/chat/server/http/api/chat/conversations/$id.workflows.$workflowId' +import route9 from '~/chat/server/http/api/chat/bot-drafts/$id.files' +import route14 from '~/chat/server/http/api/chat/bots/$id.files' +import route28 from '~/chat/server/http/api/chat/conversations/$id.files' +import route22 from '~/chat/server/http/api/chat/conversations/$id/memories' +import route23 from '~/chat/server/http/api/chat/conversations/$id/references' +import route71 from '~/chat/server/http/api/chat/project-snapshots.$hash.quarantine' +import route74 from '~/chat/server/http/api/chat/projects.$id.sync' +import route83 from '~/chat/server/http/api/chat/retries/$id.prepare' +import route11 from '~/chat/server/http/api/chat/bots/$id.$operation' +import route24 from '~/chat/server/http/api/chat/conversations/$id.$operation' +import route10 from '~/chat/server/http/api/chat/bot-drafts/$id' +import route15 from '~/chat/server/http/api/chat/bots/$id' +import route32 from '~/chat/server/http/api/chat/copies/$id' +import route68 from '~/chat/server/http/api/chat/plugins/$id' +import route72 from '~/chat/server/http/api/chat/project-snapshots.$hash' +import route75 from '~/chat/server/http/api/chat/projects.$id' +import route77 from '~/chat/server/http/api/chat/recipes/$id' +import route84 from '~/chat/server/http/api/chat/retries/$id' +import route85 from '~/chat/server/http/api/chat/sections/$id' +import route87 from '~/chat/server/http/api/chat/skills/$id' +import route92 from '~/chat/server/http/api/chat/workspace-sync/$operation' +import route63 from '~/chat/server/http/api/chat/mcp/$' +import route4 from '~/chat/server/http/api/chat/activity' +import route5 from '~/chat/server/http/api/chat/app-version' +import route6 from '~/chat/server/http/api/chat/bootstrap' +import route18 from '~/chat/server/http/api/chat/bots' +import route19 from '~/chat/server/http/api/chat/connection' +import route33 from '~/chat/server/http/api/chat/device-transport' +import route34 from '~/chat/server/http/api/chat/draft' +import route35 from '~/chat/server/http/api/chat/files' +import route65 from '~/chat/server/http/api/chat/model-options' +import route66 from '~/chat/server/http/api/chat/models' +import route69 from '~/chat/server/http/api/chat/plugins' +import route70 from '~/chat/server/http/api/chat/policy' +import route73 from '~/chat/server/http/api/chat/project-snapshots' +import route76 from '~/chat/server/http/api/chat/projects' +import route78 from '~/chat/server/http/api/chat/recipes' +import route82 from '~/chat/server/http/api/chat/references' +import route86 from '~/chat/server/http/api/chat/sections' +import route88 from '~/chat/server/http/api/chat/skills' +import route93 from '~/chat/server/http/api/chat/workspace' +import route89 from '~/chat/server/http/api/chat/workspace-history' +import route91 from '~/chat/server/http/api/chat/workspace-index' +import route94 from '~/chat/server/http/auth/mcp/callback' +import route95 from '~/chat/server/http/auth/mcp/client-metadata' + +// Specific paths precede parameters and splats, matching the Start route tree. +export const chatHttpRoutes = [ + route48, + route49, + route43, + route42, + route44, + route79, + route80, + route81, + route41, + route45, + route46, + route51, + route53, + route56, + route57, + route58, + route0, + route38, + route47, + route52, + route54, + route1, + route2, + route3, + route16, + route17, + route36, + route37, + route39, + route40, + route50, + route55, + route59, + route60, + route61, + route62, + route64, + route67, + route90, + route25, + route20, + route7, + route12, + route26, + route29, + route8, + route13, + route27, + route21, + route30, + route31, + route9, + route14, + route28, + route22, + route23, + route71, + route74, + route83, + route11, + route24, + route10, + route15, + route32, + route68, + route72, + route75, + route77, + route84, + route85, + route87, + route92, + route63, + route4, + route5, + route6, + route18, + route19, + route33, + route34, + route35, + route65, + route66, + route69, + route70, + route73, + route76, + route78, + route82, + route86, + route88, + route93, + route89, + route91, + route94, + route95, +] diff --git a/src/chat/server/routes/draft.ts b/src/chat/server/routes/draft.ts new file mode 100644 index 000000000..a5e66ae05 --- /dev/null +++ b/src/chat/server/routes/draft.ts @@ -0,0 +1,43 @@ +import { hasChatAccess } from '~/chat/access.server' +import { z } from 'zod' +import { getAuthService } from '~/auth/index.server' +import { + jsonError, + readJsonBody, + validateSameOriginRequest, +} from '~/utils/api-boundary.server' +import { composerDraftApi } from '~/chat/server/composer-drafts' + +async function handle(request: Request) { + if (request.method === 'POST') { + const rejection = validateSameOriginRequest(request) + if (rejection) return jsonError(rejection.message, rejection.status) + } + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Sign in to continue.', 401) + if (!(await hasChatAccess(user))) + return jsonError('Chat access is unavailable.', 403) + const scope = new URL(request.url).searchParams.get('scope') ?? '' + const body = + request.method === 'POST' + ? await readJsonBody(request, { maxContentLength: 600_000 }) + : undefined + if (body && !body.success) + return jsonError(body.error.message, body.error.status) + try { + return await composerDraftApi( + request, + user.userId, + scope, + body?.success ? body.body : undefined, + ) + } catch (error) { + if (error instanceof z.ZodError) + return jsonError('Invalid draft request.', 400) + throw error + } +} +export const handlers = { + GET: ({ request }: { request: Request }) => handle(request), + POST: ({ request }: { request: Request }) => handle(request), +} diff --git a/src/chat/server/routes/files.ts b/src/chat/server/routes/files.ts new file mode 100644 index 000000000..f9b08b400 --- /dev/null +++ b/src/chat/server/routes/files.ts @@ -0,0 +1,84 @@ +import { hasChatAccess } from '~/chat/access.server' +import { z } from 'zod' +import type { FileScope, DraftFileScope } from '~/chat/core/files' +import { getAuthService } from '~/auth/index.server' +import { + jsonError, + validateSameOriginRequest, +} from '~/utils/api-boundary.server' +import { fileApi } from '~/chat/server/file-api' +import { getFileEnvironment } from '~/chat/server/file-environment.server' +import { SavedFileError } from '~/chat/server/saved-file-contract' +const requestSchema = z + .object({ + workspaceId: z.string().min(1).max(1000), + botId: z.string().min(1).max(1000).optional(), + conversationId: z.string().min(1).max(1000).optional(), + draftId: z.string().uuid().optional(), + id: z.string().uuid().optional(), + content: z.enum(['0', '1']).default('0'), + }) + .superRefine((value, ctx) => { + if ( + value.draftId + ? Boolean(value.botId || value.conversationId) + : !value.botId + ) + ctx.addIssue({ + code: 'custom', + message: 'Choose a draft or a conversation.', + }) + if (value.content === '1' && !value.id) + ctx.addIssue({ code: 'custom', message: 'Choose a file.' }) + }) +export async function handleFileRequest(request: Request) { + if (request.method === 'PUT') { + const rejection = validateSameOriginRequest(request) + if (rejection) return jsonError(rejection.message, rejection.status) + } + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Sign in to continue.', 401) + if (!(await hasChatAccess(user))) + return jsonError('Chat access is unavailable.', 403) + const parsed = requestSchema.safeParse( + Object.fromEntries(new URL(request.url).searchParams), + ) + if (!parsed.success) return jsonError('Invalid file request.', 400) + const value = parsed.data + let scope: FileScope | DraftFileScope + if (value.draftId) + scope = { + workspaceId: value.workspaceId, + userId: user.userId, + draftId: value.draftId, + } + else { + if (!value.botId) return jsonError('Invalid file request.', 400) + scope = { + workspaceId: value.workspaceId, + userId: user.userId, + botId: value.botId, + conversationId: value.conversationId, + } + } + + try { + return await fileApi( + request, + await getFileEnvironment(), + scope, + value.id, + value.content === '1', + ) + } catch (error) { + if (error instanceof SavedFileError) + return jsonError(error.message, error.status) + if (error instanceof z.ZodError) + return jsonError('Invalid file request.', 400) + throw error + } +} +export const handlers = { + GET: ({ request }: { request: Request }) => handleFileRequest(request), + PUT: ({ request }: { request: Request }) => handleFileRequest(request), +} diff --git a/src/chat/server/routes/mcp/$.ts b/src/chat/server/routes/mcp/$.ts new file mode 100644 index 000000000..ad04d9a5d --- /dev/null +++ b/src/chat/server/routes/mcp/$.ts @@ -0,0 +1,62 @@ +import { hasChatAccess } from '~/chat/access.server' +import { z } from 'zod' +import { getAuthService } from '~/auth/index.server' +import { + jsonError, + validateSameOriginRequest, +} from '~/utils/api-boundary.server' +import { + readWorkspacePolicy, + WorkspacePolicyError, +} from '~/chat/workspace-policy.server' +import { + getMcpEnvironment, + McpEnvironmentError, +} from '~/chat/server/mcp-environment.server' +import { mcpAccountApi } from '~/chat/server/mcp-account-api' +import { McpAccountError } from '~/chat/server/mcp-account-contract' +import { McpAuthorizationError } from '~/chat/server/mcp-auth-network' +export async function handleMcp(request: Request) { + if (request.method === 'POST') { + const error = validateSameOriginRequest(request) + if (error) return jsonError(error.message, error.status) + } + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Sign in to continue.', 401) + if (!(await hasChatAccess(user))) + return jsonError('Chat access is unavailable.', 403) + const url = new URL(request.url) + const workspace = z + .string() + .min(1) + .max(1000) + .safeParse(url.searchParams.get('workspaceId')) + if (!workspace.success) return jsonError('Choose a workspace.', 400) + try { + await readWorkspacePolicy(workspace.data, user.userId) + const path = url.pathname.slice('/api/chat/'.length) + return ( + (await mcpAccountApi( + request, + await getMcpEnvironment(), + { workspaceId: workspace.data, userId: user.userId }, + path, + )) ?? jsonError('Not found.', 404) + ) + } catch (error) { + if ( + error instanceof WorkspacePolicyError || + error instanceof McpEnvironmentError || + error instanceof McpAccountError || + error instanceof McpAuthorizationError + ) + return jsonError(error.message, error.status) + if (error instanceof z.ZodError) + return jsonError('The connection request is invalid.', 400) + throw error + } +} +export const handlers = { + GET: ({ request }: { request: Request }) => handleMcp(request), + POST: ({ request }: { request: Request }) => handleMcp(request), +} diff --git a/src/chat/server/routes/models.ts b/src/chat/server/routes/models.ts new file mode 100644 index 000000000..a336181d0 --- /dev/null +++ b/src/chat/server/routes/models.ts @@ -0,0 +1,40 @@ +import { hasChatAccess } from '~/chat/access.server' +import { z } from 'zod' +import { getAuthService } from '~/auth/index.server' +import { jsonError, jsonResponse } from '~/utils/api-boundary.server' +import { + readWorkspacePolicy, + WorkspacePolicyError, +} from '~/chat/workspace-policy.server' +import { getModelEnvironment } from '~/chat/server/model-environment.server' +import { getRunModelCatalog, RunModelError } from '~/chat/server/run-models' +const workspaceIdSchema = z.string().min(1).max(1000) +export async function handleModelCatalog(request: Request) { + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Sign in to continue.', 401) + if (!(await hasChatAccess(user))) + return jsonError('Chat access is unavailable.', 403) + const parsed = workspaceIdSchema.safeParse( + new URL(request.url).searchParams.get('workspaceId'), + ) + if (!parsed.success) return jsonError('Choose a workspace.', 400) + try { + const policy = await readWorkspacePolicy(parsed.data, user.userId) + const env = await getModelEnvironment() + const catalog = await getRunModelCatalog(env, { + userId: user.userId, + policy, + fixture: false, + }) + return jsonResponse(catalog, { + headers: { 'Cache-Control': 'private, no-store' }, + }) + } catch (error) { + if (error instanceof WorkspacePolicyError || error instanceof RunModelError) + return jsonError(error.message, error.status) + throw error + } +} +export const handlers = { + GET: ({ request }: { request: Request }) => handleModelCatalog(request), +} diff --git a/src/chat/server/routes/openrouter/callback.ts b/src/chat/server/routes/openrouter/callback.ts new file mode 100644 index 000000000..a258b3b26 --- /dev/null +++ b/src/chat/server/routes/openrouter/callback.ts @@ -0,0 +1,34 @@ +export function openRouterCallbackResponse() { + const nonce = crypto.randomUUID() + // Do not load the app, analytics, or third-party assets while the + // authorization code is in the URL. + return new Response( + ` +Connect OpenRouter | TanChat +

Return to Builder to finish connecting OpenRouter.

+`, + { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + 'Cache-Control': 'no-store', + 'Referrer-Policy': 'no-referrer', + 'Content-Security-Policy': `default-src 'none'; script-src 'nonce-${nonce}'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'`, + 'X-Content-Type-Options': 'nosniff', + 'Cross-Origin-Opener-Policy': 'same-origin', + }, + }, + ) +} +export const handlers = { GET: () => openRouterCallbackResponse() } diff --git a/src/chat/server/routes/plugins.ts b/src/chat/server/routes/plugins.ts new file mode 100644 index 000000000..c8f539ced --- /dev/null +++ b/src/chat/server/routes/plugins.ts @@ -0,0 +1,54 @@ +import { hasChatAccess } from '~/chat/access.server' +import { z } from 'zod' +import { getAuthService } from '~/auth/index.server' +import { + jsonError, + validateSameOriginRequest, +} from '~/utils/api-boundary.server' +import { + readWorkspacePolicy, + WorkspacePolicyError, +} from '~/chat/workspace-policy.server' +import { + getKodyEnvironment, + KodyEnvironmentError, +} from '~/chat/server/kody-environment.server' +import { pluginApi } from '~/chat/server/plugin-api' +export async function handlePlugins(request: Request, id?: string) { + if (request.method === 'POST') { + const error = validateSameOriginRequest(request) + if (error) return jsonError(error.message, error.status) + } + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Sign in to continue.', 401) + if (!(await hasChatAccess(user))) + return jsonError('Chat access is unavailable.', 403) + const url = new URL(request.url) + const parsed = z + .string() + .min(1) + .max(1000) + .safeParse(url.searchParams.get('workspaceId')) + if (url.searchParams.getAll('workspaceId').length !== 1 || !parsed.success) + return jsonError('Choose a workspace.', 400) + try { + await readWorkspacePolicy(parsed.data, user.userId) + return await pluginApi( + request, + await getKodyEnvironment(), + { workspaceId: parsed.data, userId: user.userId }, + id ?? url.searchParams.get('id') ?? undefined, + ) + } catch (error) { + if ( + error instanceof WorkspacePolicyError || + error instanceof KodyEnvironmentError + ) + return jsonError(error.message, error.status) + throw error + } +} +export const handlers = { + GET: ({ request }: { request: Request }) => handlePlugins(request), + POST: ({ request }: { request: Request }) => handlePlugins(request), +} diff --git a/src/chat/server/routes/project-snapshots.$hash.quarantine.ts b/src/chat/server/routes/project-snapshots.$hash.quarantine.ts new file mode 100644 index 000000000..4367eb5ad --- /dev/null +++ b/src/chat/server/routes/project-snapshots.$hash.quarantine.ts @@ -0,0 +1,163 @@ +import { + jsonError, + jsonResponse, + validateSameOriginRequest, +} from '~/utils/api-boundary.server' +import { + purgeHostingCacheTags, + type PurgeResult, +} from '~/utils/hosting-cache.server' +import { + getBuilderProjectSnapshotCacheTag, + isBuilderProjectSnapshotHash, + isBuilderProjectSnapshotQuarantined, + BuilderProjectSnapshotStorageUnavailableError, + quarantineBuilderProjectSnapshot, +} from '~/utils/builder-project-snapshot-storage.server' +import { + BuilderProjectStorageUnavailableError, + quarantineStoredBuilderProjectsBySnapshotHash, +} from '~/utils/builder-project-storage.server' +import { quarantineBuilderProjectsBySnapshotHash } from '~/utils/builder-project-events.server' + +type BuilderProjectQuarantineOperations = { + quarantineSnapshot: (hash: string, userId: string) => Promise + isSnapshotQuarantined: (hash: string) => Promise + purgeCacheTags: (tags: Array) => Promise + quarantineProjects: (input: { + snapshotHash: string + actorId: string + }) => Promise> + quarantineStableProjects: ( + snapshotHash: string, + actorId: string, + ) => Promise +} + +export class BuilderProjectQuarantineCleanupError extends Error { + constructor(readonly failures: Array) { + super('Builder project quarantine cleanup did not finish') + this.name = 'BuilderProjectQuarantineCleanupError' + } +} + +export async function quarantineBuilderProjectSnapshotForAdmin( + { + hash, + actorId, + }: { + hash: string + actorId: string + }, + operations: BuilderProjectQuarantineOperations = { + quarantineSnapshot: quarantineBuilderProjectSnapshot, + isSnapshotQuarantined: isBuilderProjectSnapshotQuarantined, + purgeCacheTags: purgeHostingCacheTags, + quarantineProjects: quarantineBuilderProjectsBySnapshotHash, + quarantineStableProjects: quarantineStoredBuilderProjectsBySnapshotHash, + }, +) { + const failures: Array = [] + let tombstoned = false + try { + await operations.quarantineSnapshot(hash, actorId) + tombstoned = true + } catch (error) { + failures.push(error) + } + + const purge = await operations.purgeCacheTags([ + getBuilderProjectSnapshotCacheTag(hash), + ]) + if (!purge.purged) failures.push(new Error('Cache purge failed')) + if (!tombstoned) { + try { + tombstoned = await operations.isSnapshotQuarantined(hash) + } catch (statusError) { + failures.push(statusError) + } + } + if (!tombstoned) { + throw new BuilderProjectQuarantineCleanupError(failures) + } + + let projectsQuarantined: Array = [] + try { + projectsQuarantined = await operations.quarantineProjects({ + snapshotHash: hash, + actorId, + }) + } catch (error) { + failures.push(error) + } + + let stableRecordsQuarantined = 0 + try { + stableRecordsQuarantined = await operations.quarantineStableProjects( + hash, + actorId, + ) + } catch (error) { + failures.push(error) + } + + if (failures.length > 0) { + throw new BuilderProjectQuarantineCleanupError(failures) + } + return { projectsQuarantined, purge, stableRecordsQuarantined } +} +export const handlers = { + POST: async ({ + params, + request, + }: { + params: { hash: string } + request: Request + }) => { + const requestError = validateSameOriginRequest(request) + if (requestError) { + return jsonError(requestError.message, requestError.status) + } + if (!isBuilderProjectSnapshotHash(params.hash)) { + return jsonError('Builder project not found', 404) + } + + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) { + return jsonError('Not authenticated', 401) + } + if (!user.capabilities.includes('admin')) { + return jsonError('Not authorized', 403) + } + + try { + const { projectsQuarantined, purge, stableRecordsQuarantined } = + await quarantineBuilderProjectSnapshotForAdmin({ + hash: params.hash, + actorId: user.userId, + }) + return jsonResponse({ + purge, + quarantined: true, + projectsQuarantined: projectsQuarantined.length, + stableRecordsQuarantined, + }) + } catch (error) { + if (error instanceof BuilderProjectQuarantineCleanupError) { + for (const failure of error.failures) { + console.error('Builder project quarantine cleanup failed:', failure) + } + return jsonError('Builder quarantine cleanup is unavailable', 503) + } + if ( + error instanceof BuilderProjectSnapshotStorageUnavailableError || + error instanceof BuilderProjectStorageUnavailableError + ) { + return jsonError('Builder storage is unavailable', 503) + } + console.error('Failed to quarantine builder project:', error) + return jsonError('Failed to quarantine builder project', 500) + } + }, +} diff --git a/src/chat/server/routes/project-snapshots.ts b/src/chat/server/routes/project-snapshots.ts new file mode 100644 index 000000000..4376a3044 --- /dev/null +++ b/src/chat/server/routes/project-snapshots.ts @@ -0,0 +1,108 @@ +import { + jsonError, + jsonResponse, + readJsonBody, + validateJsonRequest, +} from '~/utils/api-boundary.server' +import { + BuilderProjectSnapshotQuarantinedError, + BuilderProjectSnapshotStorageUnavailableError, + parseStoredBuilderProjectSnapshot, +} from '~/utils/builder-project-snapshot-storage.server' +import { + BuilderProjectSnapshotLimitError, + BuilderProjectSnapshotRegistryConflictError, + storeBuilderProjectSnapshotForOwner, +} from '~/utils/builder-project-snapshot-registry.server' +import { + checkIpRateLimit, + RATE_LIMITS, + rateLimitedResponse, +} from '~/utils/rateLimit.server' + +const maxRequestBytes = 2 * 1024 * 1024 +export const handlers = { + POST: async ({ request }: { request: Request }) => { + const requestError = validateJsonRequest(request, { + maxContentLength: maxRequestBytes, + }) + if (requestError) { + return jsonError(requestError.message, requestError.status) + } + + const rateLimit = await checkIpRateLimit( + request, + RATE_LIMITS.builderProjectWrite, + ) + if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) + + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) { + return jsonError('Not authenticated', 401, rateLimit.headers) + } + + const body = await readJsonBody(request, { + maxContentLength: maxRequestBytes, + }) + if (!body.success) { + return jsonError(body.error.message, body.error.status, rateLimit.headers) + } + + let project + try { + project = parseStoredBuilderProjectSnapshot(body.body) + } catch (error) { + return jsonError( + error instanceof Error ? error.message : 'Invalid project snapshot', + 400, + rateLimit.headers, + ) + } + + try { + const result = await storeBuilderProjectSnapshotForOwner( + user.userId, + project, + ) + return jsonResponse( + { + hash: result.hash, + url: `/builder/p/${result.hash}`, + sourceUrl: `/api/chat/project-snapshots/${result.hash}`, + }, + { + status: result.created ? 201 : 200, + headers: rateLimit.headers, + }, + ) + } catch (error) { + if (error instanceof BuilderProjectSnapshotQuarantinedError) { + return jsonError( + 'Project snapshot is unavailable', + 410, + rateLimit.headers, + ) + } + if ( + error instanceof BuilderProjectSnapshotLimitError || + error instanceof BuilderProjectSnapshotRegistryConflictError + ) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { + return jsonError( + 'Project snapshot storage is unavailable', + 503, + rateLimit.headers, + ) + } + console.error('Failed to store Builder project snapshot:', error) + return jsonError( + 'Failed to store project snapshot', + 500, + rateLimit.headers, + ) + } + }, +} diff --git a/src/chat/server/routes/projects.$id.sync.ts b/src/chat/server/routes/projects.$id.sync.ts new file mode 100644 index 000000000..f1f7494fa --- /dev/null +++ b/src/chat/server/routes/projects.$id.sync.ts @@ -0,0 +1,536 @@ +import { + jsonError, + jsonResponse, + readJsonBody, + validateJsonRequest, + validateSameOriginRequest, +} from '~/utils/api-boundary.server' +import { + BuilderProjectConflictError, + BuilderProjectDeletedError, + BuilderProjectLeaseError, + BuilderProjectNotFoundError, + BuilderProjectOwnershipError, + BuilderProjectRevisionConflictError, + cancelPendingBuilderProjectRun, + claimBuilderProjectRun, + createBuilderProjectThread, + enqueueBuilderProjectRun, + finishBuilderProjectRun, + getBuilderProjectMutationRequestHash, + getBuilderProjectConversationSnapshotPage, + getBuilderProjectState, + heartbeatBuilderProjectRun, + importBuilderProjectTranscript, + interruptExpiredBuilderProjectRuns, + listBuilderProjectEvents, + updateBuilderProjectState, +} from '~/utils/builder-project-events.server' +import { isBuilderProjectId } from '~/utils/builder-project' +import { getOrImportBuilderProjectState } from '~/utils/builder-project-state.server' +import { + createBuilderProjectEventStreamResponse, + isBuilderProjectSyncStreamRequest, + parseBuilderProjectSyncCursor, +} from '~/utils/builder-project-sync-http.server' +import { + encodeBuilderProjectSyncSnapshotContinuation, + parseBuilderProjectSyncSnapshotContinuation, + parseBuilderProjectSyncSnapshotPage, + parseBuilderProjectSyncRequest, + type BuilderProjectSyncCommand, + type BuilderProjectSyncCommandOutcome, +} from '~/utils/builder-project-sync' +import { + BuilderProjectSnapshotQuarantinedError, + BuilderProjectSnapshotStorageUnavailableError, +} from '~/utils/builder-project-snapshot-storage.server' +import { + BuilderProjectSnapshotLimitError, + BuilderProjectSnapshotRegistryConflictError, + storeBuilderProjectSnapshotForOwner, +} from '~/utils/builder-project-snapshot-registry.server' +import { BuilderProjectStorageUnavailableError } from '~/utils/builder-project-storage.server' +import { + checkIpRateLimit, + checkUserRateLimit, + RATE_LIMITS, + rateLimitedResponse, +} from '~/utils/rateLimit.server' + +const maxRequestBytes = 2 * 1024 * 1024 + +async function applyBuilderProjectSyncCommand({ + projectId, + ownerId, + command, +}: { + projectId: string + ownerId: string + command: BuilderProjectSyncCommand +}) { + const requestHash = await getBuilderProjectMutationRequestHash(command) + switch (command.type) { + case 'project.revise': { + const snapshot = await storeBuilderProjectSnapshotForOwner( + ownerId, + command.project, + ) + const project = await updateBuilderProjectState({ + projectId, + ownerId, + clientMutationId: command.clientMutationId, + requestHash, + revisionId: command.revisionId, + snapshotHash: snapshot.hash, + title: command.project.title, + description: command.project.description, + expectedRevisionNumber: command.expectedRevisionNumber, + }) + return { + clientMutationId: command.clientMutationId, + sequence: project.lastEventSequence, + events: [], + } + } + case 'thread.create': + return createBuilderProjectThread({ + projectId, + ownerId, + id: command.thread.id, + clientMutationId: command.clientMutationId, + requestHash, + title: command.thread.title, + ...(command.thread.createdAt + ? { createdAt: new Date(command.thread.createdAt) } + : {}), + }) + case 'run.enqueue': + return enqueueBuilderProjectRun({ + projectId, + ownerId, + runId: command.run.id, + threadId: command.run.threadId, + clientMutationId: command.clientMutationId, + requestHash, + queueKind: command.run.queueKind, + provider: command.run.provider, + model: command.run.model, + userMessage: { + id: command.userMessage.id, + clientMutationId: command.userMessage.clientMutationId, + content: command.userMessage.content, + parts: command.userMessage.parts, + }, + }) + case 'run.claim': + return claimBuilderProjectRun({ + projectId, + ownerId, + runId: command.runId, + clientMutationId: command.clientMutationId, + requestHash, + leaseOwnerId: command.leaseOwnerId, + }) + case 'run.cancel': + return cancelPendingBuilderProjectRun({ + projectId, + ownerId, + runId: command.runId, + clientMutationId: command.clientMutationId, + requestHash, + browserSessionId: command.browserSessionId, + }) + case 'run.heartbeat': + return heartbeatBuilderProjectRun({ + projectId, + ownerId, + runId: command.runId, + clientMutationId: command.clientMutationId, + leaseOwnerId: command.leaseOwnerId, + fencingToken: command.leaseFencingToken, + }) + case 'run.finish': + const finishResult = await finishBuilderProjectRun({ + projectId, + ownerId, + runId: command.runId, + clientMutationId: command.clientMutationId, + requestHash, + status: command.status, + leaseOwnerId: command.leaseOwnerId, + fencingToken: command.leaseFencingToken, + ...(command.revision ? { revision: command.revision } : {}), + ...(command.error ? { error: command.error } : {}), + ...(command.activity ? { activity: command.activity } : {}), + ...(command.revision && command.assistantMessage + ? { + revisionConflictFallback: { + clientMutationId: command.assistantMessage.clientMutationId, + error: { + message: + 'The project changed before the assistant edit could be saved.', + }, + }, + } + : {}), + ...(command.assistantMessage + ? { + assistantMessage: { + id: command.assistantMessage.id, + clientMutationId: command.assistantMessage.clientMutationId, + content: command.assistantMessage.content, + parts: command.assistantMessage.parts, + ...(command.assistantMessage.createdAt + ? { + createdAt: new Date(command.assistantMessage.createdAt), + } + : {}), + }, + } + : {}), + }) + if (finishResult.revisionConflict) { + throw new BuilderProjectRevisionConflictError( + undefined, + finishResult.sequence, + ) + } + return { + clientMutationId: finishResult.clientMutationId, + sequence: finishResult.sequence, + events: finishResult.events, + ...(finishResult.leaseFencingToken === undefined + ? {} + : { leaseFencingToken: finishResult.leaseFencingToken }), + } + case 'transcript.import': + return importBuilderProjectTranscript({ + projectId, + ownerId, + clientMutationId: command.clientMutationId, + requestHash, + threads: command.threads.map((thread) => ({ + id: thread.id, + title: thread.title, + createdAt: new Date(thread.createdAt), + updatedAt: new Date(thread.updatedAt), + ...(thread.archivedAt + ? { archivedAt: new Date(thread.archivedAt) } + : {}), + })), + messages: command.messages.map((message) => ({ + id: message.id, + threadId: message.threadId, + ...(message.runId ? { runId: message.runId } : {}), + role: message.role, + content: message.content, + parts: message.parts, + position: message.position, + createdAt: new Date(message.createdAt), + updatedAt: new Date(message.updatedAt), + })), + runs: command.runs.map((run) => ({ + id: run.id, + threadId: run.threadId, + status: run.status, + provider: run.provider, + model: run.model, + ...(run.error ? { error: run.error } : {}), + ...(run.activity ? { activity: run.activity } : {}), + ...(run.startedAt ? { startedAt: new Date(run.startedAt) } : {}), + completedAt: new Date(run.completedAt), + })), + }) + } +} + +function builderProjectSyncErrorResponse(error: unknown, headers: HeadersInit) { + if (error instanceof BuilderProjectNotFoundError) { + return jsonError('Project not found', 404, headers) + } + if (error instanceof BuilderProjectOwnershipError) { + return jsonError('Not authorized', 403, headers) + } + if (error instanceof BuilderProjectSnapshotRegistryConflictError) { + return jsonError( + 'Project snapshot must be uploaded before completing the run', + 409, + headers, + ) + } + if (error instanceof BuilderProjectSnapshotQuarantinedError) { + return jsonError('Builder project is unavailable', 410, headers) + } + if (error instanceof BuilderProjectSnapshotLimitError) { + return jsonError(error.message, 409, headers) + } + if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { + return jsonError( + 'Builder project snapshot storage is unavailable', + 503, + headers, + ) + } + if (error instanceof BuilderProjectStorageUnavailableError) { + return jsonError('Builder project storage is unavailable', 503, headers) + } + if ( + error instanceof BuilderProjectConflictError || + error instanceof BuilderProjectDeletedError || + error instanceof BuilderProjectLeaseError + ) { + return jsonError(error.message, 409, headers) + } + console.error('Builder project sync failed:', error) + return jsonError('Failed to sync Builder project', 500, headers) +} +export const handlers = { + GET: async ({ + params, + request, + }: { + params: { id: string } + request: Request + }) => { + const requestError = validateSameOriginRequest(request) + if (requestError) { + return jsonError(requestError.message, requestError.status) + } + if (!isBuilderProjectId(params.id)) { + return jsonError('Project not found', 404) + } + + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Not authenticated', 401) + + const streamRequest = isBuilderProjectSyncStreamRequest(request) + const continuationValues = streamRequest + ? [] + : new URL(request.url).searchParams.getAll('continuation') + let continuation + try { + if (continuationValues.length > 1) throw new Error() + const value = continuationValues[0] + continuation = value + ? parseBuilderProjectSyncSnapshotContinuation(value) + : undefined + if (continuation && continuation.projectId !== params.id) { + throw new Error() + } + } catch { + return jsonError('Invalid Builder project sync continuation', 400) + } + const rateLimitConfig = continuation + ? RATE_LIMITS.builderProjectSyncSnapshotPage + : RATE_LIMITS.builderProjectSyncStream + const ipRateLimitConfig = continuation + ? RATE_LIMITS.builderProjectSyncSnapshotPageIp + : RATE_LIMITS.builderProjectSyncStreamIp + const rateLimit = await checkUserRateLimit( + `${user.userId}:${params.id}`, + rateLimitConfig, + ) + if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) + const ipBackstop = await checkIpRateLimit(request, ipRateLimitConfig) + if (!ipBackstop.allowed) return rateLimitedResponse(ipBackstop) + + try { + if (streamRequest) { + let cursor + try { + cursor = parseBuilderProjectSyncCursor(request) + } catch { + return jsonError( + 'Invalid Builder project sync cursor', + 400, + rateLimit.headers, + ) + } + let project + try { + project = await getBuilderProjectState({ + projectId: params.id, + ownerId: user.userId, + includeDeleted: true, + }) + } catch (error) { + if (!(error instanceof BuilderProjectNotFoundError)) throw error + await getOrImportBuilderProjectState(params.id) + project = await getBuilderProjectState({ + projectId: params.id, + ownerId: user.userId, + includeDeleted: true, + }) + } + if (cursor > project.lastEventSequence) { + return jsonError( + 'Builder project sync cursor is ahead of the project', + 400, + rateLimit.headers, + ) + } + + return createBuilderProjectEventStreamResponse({ + cursor, + signal: request.signal, + headers: rateLimit.headers, + listEvents: (afterSequence) => + listBuilderProjectEvents({ + projectId: params.id, + ownerId: user.userId, + afterSequence, + limit: 25, + }), + interruptExpiredRuns: async () => { + await interruptExpiredBuilderProjectRuns({ + projectId: params.id, + ownerId: user.userId, + includeDeleted: true, + }) + }, + }) + } + + if (!continuation) await getOrImportBuilderProjectState(params.id) + const snapshot = await getBuilderProjectConversationSnapshotPage({ + projectId: params.id, + ownerId: user.userId, + ...(continuation ? { continuation } : {}), + }) + const page = parseBuilderProjectSyncSnapshotPage({ + project: snapshot.project, + cursor: snapshot.cursor, + headCursor: snapshot.headCursor, + threads: snapshot.threads, + messages: snapshot.messages, + runs: snapshot.runs, + continuation: snapshot.continuation + ? encodeBuilderProjectSyncSnapshotContinuation(snapshot.continuation) + : null, + }) + return jsonResponse(page, { headers: rateLimit.headers }) + } catch (error) { + return builderProjectSyncErrorResponse(error, rateLimit.headers) + } + }, + POST: async ({ + params, + request, + }: { + params: { id: string } + request: Request + }) => { + const requestError = validateJsonRequest(request, { + maxContentLength: maxRequestBytes, + }) + if (requestError) { + return jsonError(requestError.message, requestError.status) + } + if (!isBuilderProjectId(params.id)) { + return jsonError('Project not found', 404) + } + + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Not authenticated', 401) + + const rateLimit = await checkUserRateLimit( + `${user.userId}:${params.id}`, + RATE_LIMITS.builderProjectSyncCommand, + ) + if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) + const ipBackstop = await checkIpRateLimit( + request, + RATE_LIMITS.builderProjectSyncCommandIp, + ) + if (!ipBackstop.allowed) return rateLimitedResponse(ipBackstop) + + const body = await readJsonBody(request, { + maxContentLength: maxRequestBytes, + }) + if (!body.success) { + return jsonError(body.error.message, body.error.status, rateLimit.headers) + } + + let input + try { + input = parseBuilderProjectSyncRequest(body.body) + } catch { + return jsonError( + 'Invalid Builder project sync request', + 400, + rateLimit.headers, + ) + } + + try { + await getOrImportBuilderProjectState(params.id) + await getBuilderProjectState({ + projectId: params.id, + ownerId: user.userId, + }) + const results: Array = [] + for (const command of input.commands) { + try { + results.push( + await applyBuilderProjectSyncCommand({ + projectId: params.id, + ownerId: user.userId, + command, + }), + ) + } catch (error) { + if (error instanceof BuilderProjectLeaseError) { + results.push({ + clientMutationId: command.clientMutationId, + rejected: true, + code: 'run-lease-invalid', + message: error.message, + }) + continue + } + if ( + command.type === 'project.revise' && + error instanceof BuilderProjectConflictError + ) { + results.push({ + clientMutationId: command.clientMutationId, + rejected: true, + code: 'project-revision-conflict', + message: error.message, + }) + continue + } + if ( + command.type === 'run.finish' && + command.revision !== undefined && + error instanceof BuilderProjectRevisionConflictError + ) { + results.push({ + clientMutationId: command.clientMutationId, + rejected: true, + code: 'project-revision-conflict', + message: error.message, + ...(error.sequence === undefined + ? {} + : { sequence: error.sequence }), + }) + continue + } + throw error + } + } + return jsonResponse( + { + cursor: Math.max(0, ...results.map((result) => result.sequence ?? 0)), + results, + }, + { headers: rateLimit.headers }, + ) + } catch (error) { + return builderProjectSyncErrorResponse(error, rateLimit.headers) + } + }, +} diff --git a/src/chat/server/routes/projects.$id.ts b/src/chat/server/routes/projects.$id.ts new file mode 100644 index 000000000..76ccc0205 --- /dev/null +++ b/src/chat/server/routes/projects.$id.ts @@ -0,0 +1,364 @@ +import { + jsonError, + jsonResponse, + readJsonBody, + validateJsonRequest, +} from '~/utils/api-boundary.server' +import { isBuilderProjectId } from '~/utils/builder-project' +import { + BuilderProjectConflictError, + BuilderProjectDeletedError, + BuilderProjectNotFoundError, + BuilderProjectOwnershipError, + deleteBuilderProjectState, + getBuilderProjectMutationRequestHash, + updateBuilderProjectState, +} from '~/utils/builder-project-events.server' +import { + getOrImportBuilderProjectState, + toBuilderProject, +} from '~/utils/builder-project-state.server' +import { BuilderProjectStorageUnavailableError } from '~/utils/builder-project-storage.server' +import { + BuilderProjectSnapshotQuarantinedError, + BuilderProjectSnapshotStorageUnavailableError, + parseStoredBuilderProjectSnapshot, +} from '~/utils/builder-project-snapshot-storage.server' +import { + BuilderProjectSnapshotLimitError, + BuilderProjectSnapshotRegistryConflictError, + storeBuilderProjectSnapshotForOwner, +} from '~/utils/builder-project-snapshot-registry.server' +import { + checkIpRateLimit, + RATE_LIMITS, + rateLimitedResponse, +} from '~/utils/rateLimit.server' + +const maxRequestBytes = 2 * 1024 * 1024 + +function parseBuilderProjectRequest(value: unknown) { + if ( + typeof value !== 'object' || + value === null || + Array.isArray(value) || + Object.keys(value).some( + (key) => + key !== 'clientMutationId' && + key !== 'expectedRevisionNumber' && + key !== 'project' && + key !== 'revisionId', + ) || + !('clientMutationId' in value) || + !('expectedRevisionNumber' in value) || + !('project' in value) || + !('revisionId' in value) || + typeof value.clientMutationId !== 'string' || + !isBuilderProjectId(value.clientMutationId) || + typeof value.expectedRevisionNumber !== 'number' || + !Number.isSafeInteger(value.expectedRevisionNumber) || + value.expectedRevisionNumber < 1 || + typeof value.revisionId !== 'string' || + !isBuilderProjectId(value.revisionId) + ) { + throw new Error('Invalid builder project request') + } + + return { + clientMutationId: value.clientMutationId, + expectedRevisionNumber: value.expectedRevisionNumber, + project: parseStoredBuilderProjectSnapshot(value.project), + revisionId: value.revisionId, + } +} + +function parseDeleteBuilderProjectRequest(value: unknown) { + if ( + typeof value !== 'object' || + value === null || + Array.isArray(value) || + Object.keys(value).some((key) => key !== 'clientMutationId') || + !('clientMutationId' in value) || + typeof value.clientMutationId !== 'string' || + !isBuilderProjectId(value.clientMutationId) + ) { + throw new Error('Invalid Builder project delete request') + } + return value.clientMutationId +} +export const handlers = { + GET: async ({ params }: { params: { id: string } }) => { + if (!isBuilderProjectId(params.id)) { + return jsonError('Project not found', 404) + } + + try { + const project = await getOrImportBuilderProjectState(params.id) + return jsonResponse({ project: toBuilderProject(project) }) + } catch (error) { + if (error instanceof BuilderProjectNotFoundError) { + return jsonError('Project not found', 404) + } + if (error instanceof BuilderProjectSnapshotQuarantinedError) { + return jsonError('Builder project is unavailable', 410) + } + if ( + error instanceof BuilderProjectSnapshotLimitError || + error instanceof BuilderProjectSnapshotRegistryConflictError + ) { + return jsonError(error.message, 409) + } + if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { + return jsonError('Builder project storage is unavailable', 503) + } + if (error instanceof BuilderProjectStorageUnavailableError) { + return jsonError('Builder project storage is unavailable', 503) + } + console.error('Failed to load builder project:', error) + return jsonError('Failed to load builder project', 500) + } + }, + PATCH: async ({ + params, + request, + }: { + params: { id: string } + request: Request + }) => { + const requestError = validateJsonRequest(request, { + maxContentLength: maxRequestBytes, + }) + if (requestError) { + return jsonError(requestError.message, requestError.status) + } + if (!isBuilderProjectId(params.id)) { + return jsonError('Project not found', 404) + } + + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Not authenticated', 401) + + const rateLimit = await checkIpRateLimit( + request, + RATE_LIMITS.builderProjectSave, + ) + if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) + + let existing + try { + existing = await getOrImportBuilderProjectState(params.id) + } catch (error) { + if (error instanceof BuilderProjectNotFoundError) { + return jsonError('Project not found', 404, rateLimit.headers) + } + if (error instanceof BuilderProjectSnapshotQuarantinedError) { + return jsonError( + 'Builder project is unavailable', + 410, + rateLimit.headers, + ) + } + if ( + error instanceof BuilderProjectSnapshotLimitError || + error instanceof BuilderProjectSnapshotRegistryConflictError + ) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { + return jsonError( + 'Builder project storage is unavailable', + 503, + rateLimit.headers, + ) + } + if (error instanceof BuilderProjectStorageUnavailableError) { + return jsonError( + 'Builder project storage is unavailable', + 503, + rateLimit.headers, + ) + } + console.error('Failed to load builder project for update:', error) + return jsonError('Failed to load builder project', 500, rateLimit.headers) + } + if (existing.ownerId !== user.userId) { + return jsonError('Not authorized', 403, rateLimit.headers) + } + + const body = await readJsonBody(request, { + maxContentLength: maxRequestBytes, + }) + if (!body.success) { + return jsonError(body.error.message, body.error.status, rateLimit.headers) + } + + let input + try { + input = parseBuilderProjectRequest(body.body) + } catch (error) { + return jsonError( + error instanceof Error ? error.message : 'Invalid builder project', + 400, + rateLimit.headers, + ) + } + + try { + const snapshot = input.project + const requestHash = await getBuilderProjectMutationRequestHash({ + type: 'project.revise', + ...input, + }) + const storedSnapshot = await storeBuilderProjectSnapshotForOwner( + user.userId, + snapshot, + ) + const project = await updateBuilderProjectState({ + clientMutationId: input.clientMutationId, + requestHash, + description: snapshot.description, + expectedRevisionNumber: input.expectedRevisionNumber, + projectId: existing.id, + revisionId: input.revisionId, + ownerId: user.userId, + snapshotHash: storedSnapshot.hash, + title: snapshot.title, + }) + return jsonResponse( + { project: toBuilderProject(project) }, + { headers: rateLimit.headers }, + ) + } catch (error) { + if ( + error instanceof BuilderProjectSnapshotLimitError || + error instanceof BuilderProjectSnapshotRegistryConflictError + ) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectConflictError) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectOwnershipError) { + return jsonError('Not authorized', 403, rateLimit.headers) + } + if ( + error instanceof BuilderProjectDeletedError || + error instanceof BuilderProjectSnapshotQuarantinedError + ) { + return jsonError( + 'Builder project is unavailable', + 410, + rateLimit.headers, + ) + } + if ( + error instanceof BuilderProjectStorageUnavailableError || + error instanceof BuilderProjectSnapshotStorageUnavailableError + ) { + return jsonError( + 'Builder storage is unavailable', + 503, + rateLimit.headers, + ) + } + console.error('Failed to update builder project:', error) + return jsonError( + 'Failed to update builder project', + 500, + rateLimit.headers, + ) + } + }, + DELETE: async ({ + params, + request, + }: { + params: { id: string } + request: Request + }) => { + const requestError = validateJsonRequest(request, { + maxContentLength: 1_024, + }) + if (requestError) { + return jsonError(requestError.message, requestError.status) + } + if (!isBuilderProjectId(params.id)) { + return jsonError('Project not found', 404) + } + + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Not authenticated', 401) + + const rateLimit = await checkIpRateLimit( + request, + RATE_LIMITS.builderProjectWrite, + ) + if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) + + const body = await readJsonBody(request, { maxContentLength: 1_024 }) + if (!body.success) { + return jsonError(body.error.message, body.error.status, rateLimit.headers) + } + let clientMutationId + try { + clientMutationId = parseDeleteBuilderProjectRequest(body.body) + } catch { + return jsonError( + 'Invalid Builder project delete request', + 400, + rateLimit.headers, + ) + } + + try { + await getOrImportBuilderProjectState(params.id) + await deleteBuilderProjectState({ + projectId: params.id, + ownerId: user.userId, + clientMutationId, + }) + return jsonResponse( + { deleted: true, id: params.id }, + { headers: rateLimit.headers }, + ) + } catch (error) { + if (error instanceof BuilderProjectNotFoundError) { + return jsonError('Project not found', 404, rateLimit.headers) + } + if (error instanceof BuilderProjectSnapshotQuarantinedError) { + return jsonError( + 'Builder project is unavailable', + 410, + rateLimit.headers, + ) + } + if ( + error instanceof BuilderProjectSnapshotLimitError || + error instanceof BuilderProjectSnapshotRegistryConflictError + ) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectOwnershipError) { + return jsonError('Not authorized', 403, rateLimit.headers) + } + if ( + error instanceof BuilderProjectStorageUnavailableError || + error instanceof BuilderProjectSnapshotStorageUnavailableError + ) { + return jsonError( + 'Builder project storage is unavailable', + 503, + rateLimit.headers, + ) + } + console.error('Failed to delete builder project:', error) + return jsonError( + 'Failed to delete builder project', + 500, + rateLimit.headers, + ) + } + }, +} diff --git a/src/chat/server/routes/projects.ts b/src/chat/server/routes/projects.ts new file mode 100644 index 000000000..77bc69349 --- /dev/null +++ b/src/chat/server/routes/projects.ts @@ -0,0 +1,275 @@ +import { + jsonError, + jsonResponse, + readJsonBody, + validateJsonRequest, +} from '~/utils/api-boundary.server' +import { + BuilderProjectLimitError, + BuilderProjectNotFoundError, + BuilderProjectConflictError, + createBuilderProjectState, + preflightBuilderProjectCreation, +} from '~/utils/builder-project-events.server' +import { + getOrImportBuilderProjectState, + listOrImportBuilderProjectStates, + reserveLegacyBuilderProjectId, + toBuilderProject, +} from '~/utils/builder-project-state.server' +import { BuilderProjectStorageUnavailableError } from '~/utils/builder-project-storage.server' +import { isBuilderProjectId } from '~/utils/builder-project' +import { + BuilderProjectSnapshotQuarantinedError, + BuilderProjectSnapshotStorageUnavailableError, + getBuilderProjectSnapshotHash, + parseStoredBuilderProjectSnapshot, +} from '~/utils/builder-project-snapshot-storage.server' +import { + BuilderProjectSnapshotLimitError, + BuilderProjectSnapshotRegistryConflictError, + storeBuilderProjectSnapshotForOwner, +} from '~/utils/builder-project-snapshot-registry.server' +import { + checkIpRateLimit, + checkUserWindowRateLimit, + RATE_LIMITS, + rateLimitedResponse, +} from '~/utils/rateLimit.server' + +const maxRequestBytes = 2 * 1024 * 1024 + +function parseBuilderProjectRequest(value: unknown) { + if ( + typeof value !== 'object' || + value === null || + Array.isArray(value) || + Object.keys(value).some( + (key) => + key !== 'clientMutationId' && + key !== 'forkedFromId' && + key !== 'id' && + key !== 'project' && + key !== 'revisionId', + ) || + !('clientMutationId' in value) || + !('id' in value) || + !('project' in value) || + !('revisionId' in value) || + typeof value.clientMutationId !== 'string' || + !isBuilderProjectId(value.clientMutationId) || + typeof value.id !== 'string' || + !isBuilderProjectId(value.id) || + typeof value.revisionId !== 'string' || + !isBuilderProjectId(value.revisionId) + ) { + throw new Error('Invalid builder project request') + } + + const forkedFromId = 'forkedFromId' in value ? value.forkedFromId : undefined + if ( + forkedFromId !== undefined && + (typeof forkedFromId !== 'string' || !isBuilderProjectId(forkedFromId)) + ) { + throw new Error('Invalid fork source builder ID') + } + + return { + clientMutationId: value.clientMutationId, + id: value.id, + project: parseStoredBuilderProjectSnapshot(value.project), + revisionId: value.revisionId, + ...(forkedFromId ? { forkedFromId } : {}), + } +} +export const handlers = { + GET: async ({ request }: { request: Request }) => { + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Not authenticated', 401) + + const rateLimit = await checkIpRateLimit( + request, + RATE_LIMITS.builderProjectList, + ) + if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) + + try { + const projects = await listOrImportBuilderProjectStates(user.userId) + return jsonResponse( + { projects: projects.map(toBuilderProject) }, + { headers: rateLimit.headers }, + ) + } catch (error) { + if (error instanceof BuilderProjectSnapshotQuarantinedError) { + return jsonError( + 'Builder project is unavailable', + 410, + rateLimit.headers, + ) + } + if ( + error instanceof BuilderProjectSnapshotLimitError || + error instanceof BuilderProjectSnapshotRegistryConflictError + ) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { + return jsonError( + 'Builder project storage is unavailable', + 503, + rateLimit.headers, + ) + } + if (error instanceof BuilderProjectStorageUnavailableError) { + return jsonError( + 'Builder project storage is unavailable', + 503, + rateLimit.headers, + ) + } + console.error('Failed to list builder projects:', error) + return jsonError( + 'Failed to list builder projects', + 500, + rateLimit.headers, + ) + } + }, + POST: async ({ request }: { request: Request }) => { + const requestError = validateJsonRequest(request, { + maxContentLength: maxRequestBytes, + }) + if (requestError) { + return jsonError(requestError.message, requestError.status) + } + + const { getAuthService } = await import('~/auth/index.server') + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Not authenticated', 401) + + const rateLimit = await checkIpRateLimit( + request, + RATE_LIMITS.builderProjectWrite, + ) + if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) + + const body = await readJsonBody(request, { + maxContentLength: maxRequestBytes, + }) + if (!body.success) { + return jsonError(body.error.message, body.error.status, rateLimit.headers) + } + + let input + try { + input = parseBuilderProjectRequest(body.body) + } catch (error) { + return jsonError( + error instanceof Error ? error.message : 'Invalid builder project', + 400, + rateLimit.headers, + ) + } + + try { + await listOrImportBuilderProjectStates(user.userId) + const snapshotHash = await getBuilderProjectSnapshotHash(input.project) + const existing = await preflightBuilderProjectCreation({ + id: input.id, + ownerId: user.userId, + clientMutationId: input.clientMutationId, + revisionId: input.revisionId, + snapshotHash, + title: input.project.title, + description: input.project.description, + ...(input.forkedFromId ? { forkedFromId: input.forkedFromId } : {}), + }) + if (existing) { + return jsonResponse( + { project: toBuilderProject(existing) }, + { status: 201, headers: rateLimit.headers }, + ) + } + const creationLimit = await checkUserWindowRateLimit( + user.userId, + RATE_LIMITS.builderProjectCreateDaily, + ) + if (!creationLimit.allowed) { + return rateLimitedResponse(creationLimit) + } + await reserveLegacyBuilderProjectId(input.id) + + if (input.forkedFromId) { + try { + await getOrImportBuilderProjectState(input.forkedFromId) + } catch (error) { + if (error instanceof BuilderProjectNotFoundError) { + return jsonError( + 'Fork source builder was not found', + 400, + rateLimit.headers, + ) + } + throw error + } + } + + const snapshot = input.project + const storedSnapshot = await storeBuilderProjectSnapshotForOwner( + user.userId, + snapshot, + ) + const state = await createBuilderProjectState({ + id: input.id, + revisionId: input.revisionId, + clientMutationId: input.clientMutationId, + description: snapshot.description, + forkedFromId: input.forkedFromId, + ownerId: user.userId, + snapshotHash: storedSnapshot.hash, + title: snapshot.title, + }) + return jsonResponse( + { project: toBuilderProject(state) }, + { status: 201, headers: rateLimit.headers }, + ) + } catch (error) { + if (error instanceof BuilderProjectLimitError) { + return jsonError(error.message, 409, rateLimit.headers) + } + if ( + error instanceof BuilderProjectSnapshotLimitError || + error instanceof BuilderProjectSnapshotRegistryConflictError + ) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectConflictError) { + return jsonError(error.message, 409, rateLimit.headers) + } + if (error instanceof BuilderProjectSnapshotQuarantinedError) { + return jsonError( + 'Builder project is unavailable', + 410, + rateLimit.headers, + ) + } + if ( + error instanceof BuilderProjectStorageUnavailableError || + error instanceof BuilderProjectSnapshotStorageUnavailableError + ) { + return jsonError( + 'Builder storage is unavailable', + 503, + rateLimit.headers, + ) + } + console.error('Failed to create builder project:', error) + return jsonError( + 'Failed to create builder project', + 500, + rateLimit.headers, + ) + } + }, +} diff --git a/src/chat/server/routes/skills.ts b/src/chat/server/routes/skills.ts new file mode 100644 index 000000000..9c324284d --- /dev/null +++ b/src/chat/server/routes/skills.ts @@ -0,0 +1,54 @@ +import { hasChatAccess } from '~/chat/access.server' +import { z } from 'zod' +import { getAuthService } from '~/auth/index.server' +import { + jsonError, + validateSameOriginRequest, +} from '~/utils/api-boundary.server' +import { + readWorkspacePolicy, + WorkspacePolicyError, +} from '~/chat/workspace-policy.server' +import { + getKodyEnvironment, + KodyEnvironmentError, +} from '~/chat/server/kody-environment.server' +import { skillApi } from '~/chat/server/skill-api' +export async function handleSkills(request: Request, id?: string) { + if (request.method === 'POST') { + const error = validateSameOriginRequest(request) + if (error) return jsonError(error.message, error.status) + } + const user = await getAuthService().getCurrentUser(request) + if (!user) return jsonError('Sign in to continue.', 401) + if (!(await hasChatAccess(user))) + return jsonError('Chat access is unavailable.', 403) + const url = new URL(request.url) + const parsed = z + .string() + .min(1) + .max(1000) + .safeParse(url.searchParams.get('workspaceId')) + if (url.searchParams.getAll('workspaceId').length !== 1 || !parsed.success) + return jsonError('Choose a workspace.', 400) + try { + await readWorkspacePolicy(parsed.data, user.userId) + return await skillApi( + request, + await getKodyEnvironment(), + { workspaceId: parsed.data, userId: user.userId }, + id ?? url.searchParams.get('id') ?? undefined, + ) + } catch (error) { + if ( + error instanceof WorkspacePolicyError || + error instanceof KodyEnvironmentError + ) + return jsonError(error.message, error.status) + throw error + } +} +export const handlers = { + GET: ({ request }: { request: Request }) => handleSkills(request), + POST: ({ request }: { request: Request }) => handleSkills(request), +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 1d6c29b84..fc6171e5a 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -849,7 +849,7 @@ function DesktopNavTrigger({ data-menu-key={group.key} className={triggerClassName} onClick={onDismiss} - preload="intent" + preload={group.key === 'chat' ? false : 'intent'} > {group.label} {group.key === 'chat' && ( diff --git a/src/components/Scarf.tsx b/src/components/Scarf.tsx index c68f7acc6..ba6524111 100644 --- a/src/components/Scarf.tsx +++ b/src/components/Scarf.tsx @@ -24,7 +24,12 @@ export function Scarf({ id, path }: { id: string; path: string }) { ) useEffect(() => { - if (!visit || lastVisit.current === visit) return + if ( + import.meta.env.MODE === 'staging' || + !visit || + lastVisit.current === visit + ) + return lastVisit.current = visit setPixel({ visit, diff --git a/src/components/ds/IconographyPage.client.tsx b/src/components/ds/IconographyPage.client.tsx new file mode 100644 index 000000000..99bccfd7e --- /dev/null +++ b/src/components/ds/IconographyPage.client.tsx @@ -0,0 +1,219 @@ +import * as React from 'react' +import { twMerge } from 'tailwind-merge' +import { + CaretDownIcon, + CheckIcon, + MagnifyingGlassIcon, + StarIcon, +} from '@phosphor-icons/react' +import type { IconWeight } from '@phosphor-icons/react' +import { copyTextToClipboard } from '~/utils/browser-effects' +import { DsPage, DsSection } from '~/components/ds/DsKit' +import { phosphorIconRegistry } from '~/components/ds/phosphor-icons.generated' +import { + Dropdown, + DropdownContent, + DropdownItem, + DropdownTrigger, +} from '~/components/ds/ui' + +const WEIGHTS: Array = [ + 'thin', + 'light', + 'regular', + 'bold', + 'fill', + 'duotone', +] +const SIZES = [16, 20, 24, 32, 48] +const PAGE_SIZE = 120 + +const triggerClass = + 'inline-flex h-10 items-center gap-2 rounded-lg border border-border-default bg-background-surface px-3 text-sm text-text-primary transition-colors hover:bg-background-subtle' + +// A small square that scales with the icon size — the visual size cue. +function SizeGlyph({ px }: { px: number }) { + const box = Math.max(8, Math.min(22, Math.round(px * 0.5))) + return ( + + ) +} + +export function IconographyPage() { + const [weight, setWeight] = React.useState('regular') + const [size, setSize] = React.useState(24) + const [query, setQuery] = React.useState('') + const [searchOpen, setSearchOpen] = React.useState(false) + const [copied, setCopied] = React.useState(null) + const [visible, setVisible] = React.useState(PAGE_SIZE) + const searchInputRef = React.useRef(null) + + React.useEffect(() => { + if (searchOpen) searchInputRef.current?.focus() + }, [searchOpen]) + + const closeSearch = React.useCallback(() => { + setQuery('') + setSearchOpen(false) + }, []) + + const filtered = React.useMemo(() => { + const q = query.trim().toLowerCase() + if (!q) return phosphorIconRegistry + return phosphorIconRegistry.filter((icon) => + icon.name.toLowerCase().includes(q), + ) + }, [query]) + + // Reset paging when the result set changes. + React.useEffect(() => { + setVisible(PAGE_SIZE) + }, [query]) + + const shown = filtered.slice(0, visible) + + // The registry keys icons by their bare name, but `NameIcon` is the export + // callers actually import — copy and advertise that one. + const handleCopy = React.useCallback(async (name: string) => { + await copyTextToClipboard(`<${name}Icon />`) + setCopied(name) + window.setTimeout(() => setCopied(null), 1200) + }, []) + + return ( + + +
+ {/* Style (weight) — dropdown with a live preview per weight */} + + + + + + {WEIGHTS.map((w) => ( + setWeight(w)}> + + {w} + {w === weight ? ( + + ) : null} + + ))} + + + + {/* Size — dropdown with a scaling size glyph per option */} + + + + + + {SIZES.map((s) => ( + setSize(s)}> + + + + {s}px + {s === size ? ( + + ) : null} + + ))} + + + + {/* Expanding search — icon grows on hover, opens into a field */} +
+ + setQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Escape' && closeSearch()} + placeholder="Search icons…" + className={twMerge( + 'min-w-0 flex-1 bg-transparent pr-3 text-sm text-text-primary placeholder-text-muted outline-none transition-opacity duration-200', + searchOpen ? 'opacity-100' : 'pointer-events-none opacity-0', + )} + /> +
+ + + {filtered.length.toLocaleString()} icons + +
+ +
+ {shown.map(({ name, Cmp }) => ( + + ))} +
+ + {shown.length < filtered.length ? ( +
+ +
+ ) : null} +
+
+ ) +} diff --git a/src/db/client.ts b/src/db/client.ts index 9563d0a7a..12e0b1e51 100644 --- a/src/db/client.ts +++ b/src/db/client.ts @@ -1,3 +1,7 @@ +import { + markRequestDatabaseUse, + timeRequestWork, +} from '../utils/request-timing.server' import { drizzle } from 'drizzle-orm/postgres-js' import postgres from 'postgres' import { AsyncLocalStorage } from 'node:async_hooks' @@ -63,6 +67,7 @@ function getRequestDb(context: DatabaseContext) { } function getDb() { + markRequestDatabaseUse() const context = databaseStorage.getStore() if (context) { return getRequestDb(context) @@ -87,7 +92,10 @@ export async function runWithDatabaseContext( return fn() } - const connectionString = await getDatabaseConnectionString() + const connectionString = await timeRequestWork( + 'db_config', + getDatabaseConnectionString, + ) if (!connectionString) { return fn() } diff --git a/src/queries/docsManifest.ts b/src/queries/docsManifest.ts new file mode 100644 index 000000000..7d307e5db --- /dev/null +++ b/src/queries/docsManifest.ts @@ -0,0 +1,16 @@ +import { queryOptions } from '@tanstack/react-query' +import { fetchDocsPathManifest } from '~/utils/docs.functions' + +export function docsPathManifestQueryOptions(request: { + repo: string + branch: string + docsRoot: string +}) { + const { repo, branch, docsRoot } = request + + return queryOptions({ + queryKey: ['docs-path-manifest', repo, branch, docsRoot], + queryFn: () => fetchDocsPathManifest({ data: { repo, branch, docsRoot } }), + staleTime: 1000 * 60 * 5, + }) +} diff --git a/src/router.tsx b/src/router.tsx index c84e08414..6acf75d4e 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -13,7 +13,7 @@ if (typeof document !== 'undefined') { Sentry.init({ dsn: 'https://ac4bfc43ff4a892f8dc7053c4a50d92f@o4507236158537728.ingest.us.sentry.io/4507236163649536', - enabled: import.meta.env.PROD, + enabled: import.meta.env.PROD && import.meta.env.MODE !== 'staging', sendDefaultPii: true, // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index bec6e5ce1..8bd63e8f2 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -40,10 +40,11 @@ import { ThemeProvider, useHtmlClass } from '~/components/ThemeProvider' import { Navbar } from '~/components/Navbar' import { Footer } from '~/components/Footer' import { ProductScarf } from '~/components/ProductScarf' -import { - BuilderRouteFrame, - BuilderRouteSkeleton, -} from '~/chat/components/projects/Loading' +const BuilderRouteContent = React.lazy(() => + import('~/chat/components/projects/Loading').then((module) => ({ + default: module.BuilderRouteContent, + })), +) import { THEME_COLORS } from '~/utils/utils' import { trackPageView } from '~/utils/analytics' import { @@ -275,9 +276,10 @@ export const Route = createRootRouteWithContext<{ }, { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, ], - scripts: import.meta.env.PROD - ? [{ children: GOOGLE_ANALYTICS_BOOTSTRAP }] - : [], + scripts: + import.meta.env.PROD && import.meta.env.MODE !== 'staging' + ? [{ children: GOOGLE_ANALYTICS_BOOTSTRAP }] + : [], } }, headers: () => DOCUMENT_CACHE_HEADERS, @@ -343,11 +345,15 @@ function ShellComponent({ children }: { children: React.ReactNode }) { const htmlClass = useHtmlClass() const routeContent = ( - - }> - {children} - - + + {pathname === '/builder' || pathname.startsWith('/builder/') ? ( + + {children} + + ) : ( + children + )} + ) return ( @@ -462,6 +468,7 @@ function PageViewTracker() { const lastTrackedPath = React.useRef(null) React.useEffect(() => { + if (import.meta.env.MODE === 'staging') return const stopTrackingHoverIntent = trackScarfExternalLinkHoverIntent() document.addEventListener('click', trackScarfExternalLinkClick, true) document.addEventListener('click', trackScarfDownloadClick, true) @@ -485,7 +492,8 @@ function PageViewTracker() { lastTrackedPath.current = pagePath trackPageView(pagePath) - trackScarfPageView(window.location.pathname) + if (import.meta.env.MODE !== 'staging') + trackScarfPageView(window.location.pathname) }, [pagePath]) return null diff --git a/src/routes/_library/$libraryId/$version.docs.$.tsx b/src/routes/_library/$libraryId/$version.docs.$.tsx index a8919cbb8..1690b432c 100644 --- a/src/routes/_library/$libraryId/$version.docs.$.tsx +++ b/src/routes/_library/$libraryId/$version.docs.$.tsx @@ -37,6 +37,7 @@ export const Route = createFileRoute('/_library/$libraryId/$version/docs/$')({ const docsRoot = library.docsRoot || 'docs' const requestedDocsPath = docsPath ?? '' const result = await loadDocsRoute({ + queryClient: ctx.context.queryClient, repo: library.repo, branch, docsRoot, @@ -126,6 +127,7 @@ export const Route = createFileRoute('/_library/$libraryId/$version/docs/$')({ return { ...getDocsCacheHeaders({ libraryId, version }), + 'X-Docs-Html': loaderData?.isFallback ? 'no-store' : '1', ...getDocsEmbedRuntimeHeaders({ content: loaderData?.content ?? '', version, diff --git a/src/routes/_library/$libraryId/$version.docs.framework.$framework.$.tsx b/src/routes/_library/$libraryId/$version.docs.framework.$framework.$.tsx index f028d6fac..c35243ddc 100644 --- a/src/routes/_library/$libraryId/$version.docs.framework.$framework.$.tsx +++ b/src/routes/_library/$libraryId/$version.docs.framework.$framework.$.tsx @@ -36,6 +36,7 @@ export const Route = createFileRoute( const docsRoot = library.docsRoot || 'docs' const requestedDocsPath = `framework/${framework}/${docsPath ?? ''}` const result = await loadDocsRoute({ + queryClient: ctx.context.queryClient, repo: library.repo, branch, docsRoot, @@ -83,6 +84,7 @@ export const Route = createFileRoute( return { ...getDocsCacheHeaders({ libraryId, version }), + 'X-Docs-Html': loaderData?.isFallback ? 'no-store' : '1', ...getDocsEmbedRuntimeHeaders({ content: loaderData?.content ?? '', version, diff --git a/src/routes/admin/chat-access.tsx b/src/routes/admin/chat-access.tsx index b46fc56af..2eb894f2c 100644 --- a/src/routes/admin/chat-access.tsx +++ b/src/routes/admin/chat-access.tsx @@ -35,7 +35,7 @@ export const Route = createFileRoute('/admin/chat-access')({ component: ChatAccessAdminPage, }) -export function ChatAccessAdminPage() { +function ChatAccessAdminPage() { const guard = useAdminGuard() const queryClient = useQueryClient() const [tab, setTab] = useState<'waitlist' | 'invites'>('waitlist') diff --git a/src/routes/api/application-starter/compile-attributed.ts b/src/routes/api/application-starter/compile-attributed.ts index 4eab27ac3..829d53f91 100644 --- a/src/routes/api/application-starter/compile-attributed.ts +++ b/src/routes/api/application-starter/compile-attributed.ts @@ -1,50 +1,12 @@ import { createFileRoute } from '@tanstack/react-router' -import { - applicationStarterErrorResponse, - applicationStarterInternalErrorResponse, - applicationStarterJsonResponse, - readApplicationStarterJsonRequest, -} from '~/application-starter/api/request-boundary.server' -import { - applicationStarterValidateBodySchema, - parseApplicationStarterRequest, -} from '~/application-starter/api/request-schema.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -export const Route = createFileRoute('/api/application-starter/compile-attributed')({ +export const Route = createFileRoute( + '/api/application-starter/compile-attributed', +)({ server: { handlers: { - POST: async ({ request }: { request: Request }) => { - try { - const requestBody = await readApplicationStarterJsonRequest(request) - if ('response' in requestBody) { - return requestBody.response - } - - let body - try { - body = parseApplicationStarterRequest( - applicationStarterValidateBodySchema, - requestBody.body, - ) - } catch { - return applicationStarterErrorResponse( - 'Invalid request body', - 400, - requestBody.rateLimit, - ) - } - - const { compileWithAttributionHandler } = await import( - '~/application-starter/api/compile' - ) - const response = await compileWithAttributionHandler(body.definition) - - return applicationStarterJsonResponse(response, requestBody.rateLimit) - } catch (error) { - console.error('Error compiling project with attribution:', error) - return applicationStarterInternalErrorResponse('Failed to compile project') - } - }, + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, }) diff --git a/src/routes/api/application-starter/compile.ts b/src/routes/api/application-starter/compile.ts index 18fb22cfb..55f95454b 100644 --- a/src/routes/api/application-starter/compile.ts +++ b/src/routes/api/application-starter/compile.ts @@ -1,49 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { - applicationStarterErrorResponse, - applicationStarterInternalErrorResponse, - applicationStarterJsonResponse, - readApplicationStarterJsonRequest, -} from '~/application-starter/api/request-boundary.server' -import { - applicationStarterCompileBodySchema, - parseApplicationStarterRequest, -} from '~/application-starter/api/request-schema.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/application-starter/compile')({ server: { handlers: { - POST: async ({ request }: { request: Request }) => { - try { - const requestBody = await readApplicationStarterJsonRequest(request) - if ('response' in requestBody) { - return requestBody.response - } - - let body - try { - body = parseApplicationStarterRequest( - applicationStarterCompileBodySchema, - requestBody.body, - ) - } catch { - return applicationStarterErrorResponse( - 'Invalid request body', - 400, - requestBody.rateLimit, - ) - } - - const { compileHandler } = await import('~/application-starter/api/compile') - const response = await compileHandler(body.definition, { - format: body.format, - }) - return applicationStarterJsonResponse(response, requestBody.rateLimit) - } catch (error) { - console.error('Error compiling project:', error) - return applicationStarterInternalErrorResponse('Failed to compile project') - } - }, + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, }) diff --git a/src/routes/api/application-starter/deploy/check-name.ts b/src/routes/api/application-starter/deploy/check-name.ts index fd7d65133..ced4bd6e0 100644 --- a/src/routes/api/application-starter/deploy/check-name.ts +++ b/src/routes/api/application-starter/deploy/check-name.ts @@ -1,84 +1,12 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { validateApplicationStarterGetRequest } from "~/application-starter/api/request-boundary.server"; -import { jsonResponse } from "~/utils/api-boundary.server"; -import { RATE_LIMITS } from "~/utils/rateLimit.server"; +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -export const Route = createFileRoute("/api/application-starter/deploy/check-name")({ +export const Route = createFileRoute( + '/api/application-starter/deploy/check-name', +)({ server: { handlers: { - GET: async ({ request }: { request: Request }) => { - const requestGuard = await validateApplicationStarterGetRequest(request, { - rateLimit: RATE_LIMITS.deploy, - }); - if ("response" in requestGuard) { - return requestGuard.response; - } - - const [ - { getAuthService }, - { getGitHubAuthState }, - { checkRepoNameAvailable, validateRepoName }, - ] = await Promise.all([ - import("~/auth/index.server"), - import("~/auth/github.server"), - import("~/utils/github-repo.server"), - ]); - const url = new URL(request.url); - const name = url.searchParams.get("name"); - - if (!name) { - return jsonResponse( - { available: false, error: "Name required" }, - { status: 400, headers: requestGuard.rateLimit.headers }, - ); - } - - const validation = validateRepoName(name); - if (!validation.valid) { - return jsonResponse( - { - available: false, - error: validation.error ?? "Invalid repository name", - }, - { status: 400, headers: requestGuard.rateLimit.headers }, - ); - } - - const authService = getAuthService(); - const user = await authService.getCurrentUser(request); - - if (!user) { - return jsonResponse( - { available: false, error: "Not authenticated" }, - { status: 401, headers: requestGuard.rateLimit.headers }, - ); - } - - const authState = await getGitHubAuthState(user.userId); - - if (!authState.hasRepoScope || !authState.accessToken) { - return jsonResponse( - { available: false, error: "Missing scope" }, - { status: 403, headers: requestGuard.rateLimit.headers }, - ); - } - - try { - const available = await checkRepoNameAvailable( - authState.accessToken, - name, - ); - return jsonResponse( - { available }, - { headers: requestGuard.rateLimit.headers }, - ); - } catch { - return jsonResponse( - { available: false, error: "Could not check repository name" }, - { status: 502, headers: requestGuard.rateLimit.headers }, - ); - } - }, + GET: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, -}); +}) diff --git a/src/routes/api/application-starter/deploy/github.ts b/src/routes/api/application-starter/deploy/github.ts index b3234607e..60bac4587 100644 --- a/src/routes/api/application-starter/deploy/github.ts +++ b/src/routes/api/application-starter/deploy/github.ts @@ -1,332 +1,11 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { - jsonResponse, - readJsonBody, - validateJsonRequest, -} from "~/utils/api-boundary.server"; -import { - applicationStarterDeployBodySchema, - parseApplicationStarterRequest, -} from "~/application-starter/api/request-schema.server"; -import { - checkIpRateLimit, - rateLimitedResponse, - RATE_LIMITS, -} from "~/utils/rateLimit.server"; +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -interface DeployResponse { - success: true; - repoUrl: string; - owner: string; - repoName: string; -} - -interface DeployError { - success: false; - error: string; - code: - | "NOT_AUTHENTICATED" - | "NO_GITHUB_ACCOUNT" - | "MISSING_REPO_SCOPE" - | "INVALID_REPO_NAME" - | "REPO_NAME_TAKEN" - | "REPO_CREATION_FAILED" - | "PUSH_FAILED" - | "COMPILE_FAILED" - | "INVALID_REQUEST"; -} - -function deployErrorResponse(error: DeployError, status: number, headers?: Headers) { - return jsonResponse(error, { status, headers }); -} - -export const Route = createFileRoute("/api/application-starter/deploy/github")({ +export const Route = createFileRoute('/api/application-starter/deploy/github')({ server: { handlers: { - /** - * GET: Check GitHub auth state for current user - */ - GET: async ({ request }: { request: Request }) => { - const [{ getAuthService }, { getGitHubAuthState }] = await Promise.all([ - import("~/auth/index.server"), - import("~/auth/github.server"), - ]); - const authService = getAuthService(); - const user = await authService.getCurrentUser(request); - - if (!user) { - return jsonResponse({ - authenticated: false, - hasGitHubAccount: false, - hasRepoScope: false, - }); - } - - const authState = await getGitHubAuthState(user.userId); - - return jsonResponse({ - authenticated: true, - ...authState, - }); - }, - - /** - * POST: Create GitHub repo and push project files - */ - POST: async ({ request }: { request: Request }) => { - const [authIndex, authGithub, githubRepo] = await Promise.all([ - import("~/auth/index.server"), - import("~/auth/github.server"), - import("~/utils/github-repo.server"), - ]); - const { getAuthService } = authIndex; - const { getGitHubAuthState } = authGithub; - const { - createRepository, - pushFiles, - validateRepoName, - validateGitHubFiles, - generateRepoDescription, - } = githubRepo; - // Rate limiting (10 requests/minute per IP) - const rateLimit = await checkIpRateLimit(request, RATE_LIMITS.deploy); - if (!rateLimit.allowed) { - return rateLimitedResponse(rateLimit); - } - - const guardError = validateJsonRequest(request, { - maxContentLength: 1024 * 1024, - }); - if (guardError) { - return deployErrorResponse( - { - success: false, - error: guardError.message, - code: "INVALID_REQUEST", - }, - guardError.status, - rateLimit.headers, - ); - } - - const authService = getAuthService(); - const user = await authService.getCurrentUser(request); - - if (!user) { - return deployErrorResponse( - { - success: false, - error: "You must be logged in to deploy", - code: "NOT_AUTHENTICATED", - }, - 401, - rateLimit.headers, - ); - } - - const authState = await getGitHubAuthState(user.userId); - - if (!authState.hasGitHubAccount) { - return deployErrorResponse( - { - success: false, - error: "No GitHub account linked", - code: "NO_GITHUB_ACCOUNT", - }, - 403, - rateLimit.headers, - ); - } - - if (!authState.hasRepoScope || !authState.accessToken) { - console.log("[Deploy] Auth state check failed:", { - hasRepoScope: authState.hasRepoScope, - hasToken: !!authState.accessToken, - }); - return deployErrorResponse( - { - success: false, - error: - "Missing public_repo scope. Please re-authenticate with GitHub.", - code: "MISSING_REPO_SCOPE", - }, - 403, - rateLimit.headers, - ); - } - console.log( - "[Deploy] Auth state OK, hasRepoScope:", - authState.hasRepoScope, - ); - - let body; - try { - const bodyResult = await readJsonBody(request, { - maxContentLength: 1024 * 1024, - }); - if (!bodyResult.success) { - throw new Error(bodyResult.error.message); - } - body = parseApplicationStarterRequest(applicationStarterDeployBodySchema, bodyResult.body); - } catch { - return deployErrorResponse( - { - success: false, - error: "Invalid request body", - code: "INVALID_REQUEST", - }, - 400, - rateLimit.headers, - ); - } - - const { - repoName, - isPrivate, - projectName, - framework, - packageManager, - features, - featureOptions, - tailwind, - files, - } = body; - const safeFeatureOptions = featureOptions ?? {}; - - // Validate repo name - const validation = validateRepoName(repoName); - if (!validation.valid) { - return deployErrorResponse( - { - success: false, - error: validation.error ?? "Invalid repository name", - code: "INVALID_REPO_NAME", - }, - 400, - rateLimit.headers, - ); - } - - // Compile the project, or use files generated by the client-only applicationStarter. - let compiledFiles: Record; - if (files) { - const fileValidation = validateGitHubFiles(files); - if (!fileValidation.valid) { - return deployErrorResponse( - { - success: false, - error: fileValidation.error, - code: "INVALID_REQUEST", - }, - 400, - rateLimit.headers, - ); - } - compiledFiles = files; - } else { - try { - const { compileHandler } = await import("~/application-starter/api/compile"); - const result = await compileHandler({ - name: projectName, - framework, - packageManager, - tailwind, - features, - featureOptions: safeFeatureOptions, - }); - compiledFiles = result.files; - } catch (error) { - console.error("[Deploy] Compile failed:", error); - return deployErrorResponse( - { - success: false, - error: "Failed to compile project", - code: "COMPILE_FAILED", - }, - 500, - rateLimit.headers, - ); - } - } - - const compiledFileValidation = validateGitHubFiles(compiledFiles); - if (!compiledFileValidation.valid) { - return deployErrorResponse( - { - success: false, - error: compiledFileValidation.error, - code: "INVALID_REQUEST", - }, - 400, - rateLimit.headers, - ); - } - - // Create the repository - const description = generateRepoDescription(features); - console.log("[Deploy] Creating repository:", { - repoName, - isPrivate, - description, - }); - const createResult = await createRepository(authState.accessToken, { - name: repoName, - description, - isPrivate, - }); - - if (!createResult.success) { - console.error("[Deploy] Repository creation failed:", { - error: createResult.error, - code: createResult.code, - }); - const code = - createResult.code === "NAME_TAKEN" - ? "REPO_NAME_TAKEN" - : "REPO_CREATION_FAILED"; - return deployErrorResponse( - { - success: false, - error: createResult.error, - code, - }, - code === "REPO_NAME_TAKEN" ? 409 : 500, - rateLimit.headers, - ); - } - console.log("[Deploy] Repository created:", createResult.repoUrl); - - // Push files to the repository - const pushResult = await pushFiles(authState.accessToken, { - owner: createResult.owner, - repo: createResult.name, - files: compiledFiles, - message: "Initial commit from TanStack Application Starter", - }); - - if (!pushResult.success) { - console.error("[Deploy] Push failed:", pushResult.error); - return deployErrorResponse( - { - success: false, - error: `Repository created but failed to push files: ${pushResult.error}`, - code: "PUSH_FAILED", - }, - 500, - rateLimit.headers, - ); - } - - return jsonResponse( - { - success: true, - repoUrl: createResult.repoUrl, - owner: createResult.owner, - repoName: createResult.name, - } satisfies DeployResponse, - { headers: rateLimit.headers }, - ); - }, + GET: ({ request }) => forwardWorkerRequest('STARTER', request), + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, -}); +}) diff --git a/src/routes/api/application-starter/download.ts b/src/routes/api/application-starter/download.ts index 2b343171c..e7cb00de5 100644 --- a/src/routes/api/application-starter/download.ts +++ b/src/routes/api/application-starter/download.ts @@ -1,142 +1,10 @@ -import { createFileRoute } from "@tanstack/react-router"; -import JSZip from "jszip"; -import { - getContentDispositionHeader, - sanitizeResponseFilename, -} from "~/utils/http-response"; -import { - applicationStarterErrorResponse, - applicationStarterInternalErrorResponse, - validateApplicationStarterGetRequest, -} from "~/application-starter/api/request-boundary.server"; +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -const BASE64_PREFIX = "base64::"; -const MAX_BUILDER_DOWNLOAD_FEATURES = 80; -const MAX_BUILDER_DOWNLOAD_OPTIONS = 200; -const BUILDER_ID_PATTERN = /^[a-z0-9._-]{1,120}$/i; -const BUILDER_PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,119}$/; - -function decodeBase64File(content: string): Uint8Array | null { - if (content.startsWith(BASE64_PREFIX)) { - const decoded = atob(content.slice(BASE64_PREFIX.length)); - - return Uint8Array.from(decoded, (char) => char.charCodeAt(0)); - } - - return null; -} - -export const Route = createFileRoute("/api/application-starter/download")({ +export const Route = createFileRoute('/api/application-starter/download')({ server: { handlers: { - GET: async ({ request }: { request: Request }) => { - try { - const requestGuard = await validateApplicationStarterGetRequest(request); - if ("response" in requestGuard) { - return requestGuard.response; - } - - const url = new URL(request.url); - const name = url.searchParams.get("name") || "my-tanstack-app"; - const featuresParam = url.searchParams.get("features") || ""; - const framework = url.searchParams.get("framework") || "react"; - const packageManager = url.searchParams.get("pm") || "pnpm"; - const tailwind = url.searchParams.get("tailwind") !== "false"; - - const features = featuresParam - .split(",") - .map((f) => f.trim()) - .filter(Boolean); - - if ( - !BUILDER_PROJECT_NAME_PATTERN.test(name) || - features.length > MAX_BUILDER_DOWNLOAD_FEATURES || - features.some((feature) => !BUILDER_ID_PATTERN.test(feature)) - ) { - return applicationStarterErrorResponse( - "Invalid applicationStarter download request", - 400, - requestGuard.rateLimit, - ); - } - - // Parse feature options (keys like "drizzle.database=postgres") - const featureOptions: Record> = {}; - let optionCount = 0; - for (const [key, value] of url.searchParams.entries()) { - if (key.includes(".") && value) { - const [featureId, optionKey] = key.split("."); - optionCount++; - if ( - optionCount > MAX_BUILDER_DOWNLOAD_OPTIONS || - !BUILDER_ID_PATTERN.test(featureId) || - !BUILDER_ID_PATTERN.test(optionKey) || - value.length > 500 - ) { - return applicationStarterErrorResponse( - "Invalid applicationStarter download options", - 400, - requestGuard.rateLimit, - ); - } - if (!featureOptions[featureId]) { - featureOptions[featureId] = {}; - } - featureOptions[featureId][optionKey] = value; - } - } - - const { compileHandler } = await import("~/application-starter/api/compile"); - const result = await compileHandler({ - name, - framework: framework === "solid" ? "solid" : "react", - packageManager: - packageManager === "bun" || - packageManager === "npm" || - packageManager === "yarn" - ? packageManager - : "pnpm", - tailwind, - features, - featureOptions, - }); - - const zip = new JSZip(); - const zipFilename = sanitizeResponseFilename(`${name}.zip`, "app.zip"); - const rootFolderName = sanitizeResponseFilename(name, "app"); - const rootFolder = zip.folder(rootFolderName); - if (!rootFolder) { - throw new Error("Failed to create ZIP folder"); - } - - for (const [filePath, content] of Object.entries(result.files)) { - // Handle base64-encoded binary files (SVGs, images, etc.) - const binaryContent = decodeBase64File(content); - if (binaryContent) { - rootFolder.file(filePath, binaryContent, { binary: true }); - } else { - rootFolder.file(filePath, content); - } - } - - const blob = await zip.generateAsync({ type: "arraybuffer" }); - - return new Response(blob, { - headers: { - "Content-Type": "application/zip", - "Content-Disposition": getContentDispositionHeader( - "attachment", - zipFilename, - "app.zip", - ), - "Cache-Control": "no-store", - }, - }); - } catch (error) { - console.error("Error generating ZIP:", error); - return applicationStarterInternalErrorResponse("Failed to generate ZIP"); - } - }, + GET: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, -}); +}) diff --git a/src/routes/api/application-starter/feature-artifacts.ts b/src/routes/api/application-starter/feature-artifacts.ts index e721cd0ce..51a8e83c5 100644 --- a/src/routes/api/application-starter/feature-artifacts.ts +++ b/src/routes/api/application-starter/feature-artifacts.ts @@ -1,59 +1,12 @@ import { createFileRoute } from '@tanstack/react-router' -import { normalizeFrameworkId } from '~/application-starter/frameworks' -import { - applicationStarterErrorResponse, - applicationStarterInternalErrorResponse, - applicationStarterJsonResponse, - readApplicationStarterJsonRequest, -} from '~/application-starter/api/request-boundary.server' -import { - applicationStarterFeatureArtifactsBodySchema, - parseApplicationStarterRequest, -} from '~/application-starter/api/request-schema.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -export const Route = createFileRoute('/api/application-starter/feature-artifacts')({ +export const Route = createFileRoute( + '/api/application-starter/feature-artifacts', +)({ server: { handlers: { - POST: async ({ request }: { request: Request }) => { - try { - const requestBody = await readApplicationStarterJsonRequest(request) - if ('response' in requestBody) { - return requestBody.response - } - - let body - try { - body = parseApplicationStarterRequest( - applicationStarterFeatureArtifactsBodySchema, - requestBody.body, - ) - } catch { - return applicationStarterErrorResponse( - 'Invalid request body', - 400, - requestBody.rateLimit, - ) - } - - const { featureArtifactsHandler } = await import( - '~/application-starter/api/feature-artifacts' - ) - const response = await featureArtifactsHandler({ - features: body.features, - projectName: body.projectName, - framework: normalizeFrameworkId(body.framework), - featureOptions: body.featureOptions, - tailwind: body.tailwind, - }) - - return applicationStarterJsonResponse(response, requestBody.rateLimit) - } catch (error) { - console.error('Error fetching feature artifacts:', error) - return applicationStarterInternalErrorResponse( - 'Failed to fetch feature artifacts', - ) - } - }, + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, }) diff --git a/src/routes/api/application-starter/features.ts b/src/routes/api/application-starter/features.ts index 981941ce0..5f4bf86c1 100644 --- a/src/routes/api/application-starter/features.ts +++ b/src/routes/api/application-starter/features.ts @@ -1,31 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { normalizeFrameworkId } from '~/application-starter/frameworks' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/application-starter/features')({ server: { handlers: { - GET: async ({ request }: { request: Request }) => { - try { - const url = new URL(request.url) - const framework = normalizeFrameworkId( - url.searchParams.get('framework') ?? 'react', - ) - const { getFeaturesHandler } = await import('~/application-starter/api/features') - const response = await getFeaturesHandler(framework) - return new Response(JSON.stringify(response), { - headers: { 'Content-Type': 'application/json' }, - }) - } catch (error) { - console.error('Error fetching features:', error) - return new Response( - JSON.stringify({ error: 'Failed to fetch features' }), - { - status: 500, - headers: { 'Content-Type': 'application/json' }, - }, - ) - } - }, + GET: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, }) diff --git a/src/routes/api/application-starter/load-remote-addon.ts b/src/routes/api/application-starter/load-remote-addon.ts index 851657737..18a328eeb 100644 --- a/src/routes/api/application-starter/load-remote-addon.ts +++ b/src/routes/api/application-starter/load-remote-addon.ts @@ -1,35 +1,12 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { - applicationStarterErrorResponse, - applicationStarterJsonResponse, - validateApplicationStarterGetRequest, -} from "~/application-starter/api/request-boundary.server"; -import { RATE_LIMITS } from "~/utils/rateLimit.server"; +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -export const Route = createFileRoute("/api/application-starter/load-remote-addon")({ +export const Route = createFileRoute( + '/api/application-starter/load-remote-addon', +)({ server: { handlers: { - GET: async ({ request }: { request: Request }) => { - const requestGuard = await validateApplicationStarterGetRequest(request, { - rateLimit: RATE_LIMITS.applicationStarterRemote, - }); - if ("response" in requestGuard) { - return requestGuard.response; - } - - const url = new URL(request.url); - const integrationUrl = url.searchParams.get("url"); - - if (!integrationUrl) { - return applicationStarterErrorResponse("URL is required", 400, requestGuard.rateLimit); - } - - const { loadRemoteIntegrationHandler } = await import( - "~/application-starter/api/remote", - ); - const response = await loadRemoteIntegrationHandler(integrationUrl); - return applicationStarterJsonResponse(response, requestGuard.rateLimit); - }, + GET: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, -}); +}) diff --git a/src/routes/api/application-starter/load-remote-template.ts b/src/routes/api/application-starter/load-remote-template.ts index efd90f95d..5e3028a76 100644 --- a/src/routes/api/application-starter/load-remote-template.ts +++ b/src/routes/api/application-starter/load-remote-template.ts @@ -1,33 +1,12 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { - applicationStarterErrorResponse, - applicationStarterJsonResponse, - validateApplicationStarterGetRequest, -} from "~/application-starter/api/request-boundary.server"; -import { RATE_LIMITS } from "~/utils/rateLimit.server"; +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -export const Route = createFileRoute("/api/application-starter/load-remote-template")({ +export const Route = createFileRoute( + '/api/application-starter/load-remote-template', +)({ server: { handlers: { - GET: async ({ request }: { request: Request }) => { - const requestGuard = await validateApplicationStarterGetRequest(request, { - rateLimit: RATE_LIMITS.applicationStarterRemote, - }); - if ("response" in requestGuard) { - return requestGuard.response; - } - - const url = new URL(request.url); - const templateUrl = url.searchParams.get("url"); - - if (!templateUrl) { - return applicationStarterErrorResponse("URL is required", 400, requestGuard.rateLimit); - } - - const { loadRemoteTemplateHandler } = await import("~/application-starter/api/remote"); - const response = await loadRemoteTemplateHandler(templateUrl); - return applicationStarterJsonResponse(response, requestGuard.rateLimit); - }, + GET: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, -}); +}) diff --git a/src/routes/api/application-starter/load-template.ts b/src/routes/api/application-starter/load-template.ts index cd3d59b90..a50f792c3 100644 --- a/src/routes/api/application-starter/load-template.ts +++ b/src/routes/api/application-starter/load-template.ts @@ -1,51 +1,10 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { - applicationStarterErrorResponse, - applicationStarterJsonResponse, - readApplicationStarterJsonRequest, -} from "~/application-starter/api/request-boundary.server"; -import { - applicationStarterRemoteLoadBodySchema, - parseApplicationStarterRequest, -} from "~/application-starter/api/request-schema.server"; -import { RATE_LIMITS } from "~/utils/rateLimit.server"; +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -export const Route = createFileRoute("/api/application-starter/load-template")({ +export const Route = createFileRoute('/api/application-starter/load-template')({ server: { handlers: { - POST: async ({ request }: { request: Request }) => { - try { - const requestBody = await readApplicationStarterJsonRequest(request, { - rateLimit: RATE_LIMITS.applicationStarterRemote, - }); - if ("response" in requestBody) { - return requestBody.response; - } - - let body; - try { - body = parseApplicationStarterRequest( - applicationStarterRemoteLoadBodySchema, - requestBody.body, - ); - } catch { - return applicationStarterErrorResponse( - "Invalid request body", - 400, - requestBody.rateLimit, - ); - } - - const { loadRemoteTemplateHandler } = await import( - "~/application-starter/api/remote", - ); - const response = await loadRemoteTemplateHandler(body.url); - return applicationStarterJsonResponse(response, requestBody.rateLimit); - } catch (error) { - console.error("Error loading remote template:", error); - return applicationStarterErrorResponse("Failed to load remote template", 500); - } - }, + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, -}); +}) diff --git a/src/routes/api/application-starter/resolve.ts b/src/routes/api/application-starter/resolve.ts index 5e8975635..edb676170 100644 --- a/src/routes/api/application-starter/resolve.ts +++ b/src/routes/api/application-starter/resolve.ts @@ -1,206 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { - checkIpWindowRateLimit, - checkIpRateLimit, - getIpWindowRateLimitStatus, - RATE_LIMITS, - rateLimitedResponse, -} from '~/utils/rateLimit.server' -import { envFunctions } from '~/utils/env.functions' -import { - isRecord, - readJsonBody, - validateJsonRequest, -} from '~/utils/api-boundary.server' - -const MAX_APPLICATION_STARTER_BODY_BYTES = 16 * 1024 +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/application-starter/resolve')({ server: { handlers: { - GET: async ({ request }: { request: Request }) => { - try { - if (!envFunctions.DATABASE_URL) { - return new Response( - JSON.stringify({ - authenticated: false, - anonymousGenerationQuota: { - limit: RATE_LIMITS.applicationStarterAnonymousDaily.limit, - remaining: RATE_LIMITS.applicationStarterAnonymousDaily.limit, - resetAt: new Date( - Date.now() + - RATE_LIMITS.applicationStarterAnonymousDaily.windowMs, - ).toISOString(), - }, - }), - { - headers: { - 'Cache-Control': 'no-store', - 'Content-Type': 'application/json', - }, - }, - ) - } - - const [{ getAuthService }, anonymousQuota] = await Promise.all([ - import('~/auth/index.server'), - getIpWindowRateLimitStatus( - request, - RATE_LIMITS.applicationStarterAnonymousDaily, - ), - ]) - - const authService = getAuthService() - const user = await authService.getCurrentUser(request) - - return new Response( - JSON.stringify({ - authenticated: !!user, - anonymousGenerationQuota: user - ? null - : { - limit: anonymousQuota.limit, - remaining: anonymousQuota.remaining, - resetAt: anonymousQuota.resetAt.toISOString(), - }, - }), - { - headers: { - 'Cache-Control': 'no-store', - 'Content-Type': 'application/json', - }, - }, - ) - } catch (error) { - console.error('Error loading application starter status:', error) - - return new Response( - JSON.stringify({ - error: 'Failed to load application starter status', - }), - { - status: 500, - headers: { - 'Cache-Control': 'no-store', - 'Content-Type': 'application/json', - }, - }, - ) - } - }, - POST: async ({ request }: { request: Request }) => { - try { - const [{ getAuthService }, starterServer] = await Promise.all([ - import('~/auth/index.server'), - import('~/utils/application-starter.server'), - ]) - - const rateLimit = await checkIpRateLimit( - request, - RATE_LIMITS.applicationStarter, - ) - - if (!rateLimit.allowed) { - return rateLimitedResponse(rateLimit) - } - - const requestGuardError = validateJsonRequest(request, { - maxContentLength: MAX_APPLICATION_STARTER_BODY_BYTES, - }) - if (requestGuardError) { - return new Response( - JSON.stringify({ error: requestGuardError.message }), - { - status: requestGuardError.status, - headers: { - 'Content-Type': 'application/json', - ...Object.fromEntries(rateLimit.headers.entries()), - }, - }, - ) - } - - const bodyResult = await readJsonBody(request, { - maxContentLength: MAX_APPLICATION_STARTER_BODY_BYTES, - }) - if (!bodyResult.success) { - return new Response( - JSON.stringify({ error: bodyResult.error.message }), - { - status: bodyResult.error.status, - headers: { - 'Content-Type': 'application/json', - ...Object.fromEntries(rateLimit.headers.entries()), - }, - }, - ) - } - - const rawBody = bodyResult.body - const isAnalyzeRequest = - isRecord(rawBody) && rawBody.mode === 'analyze' - const body = starterServer.applicationStarterRequestSchema.parse(rawBody) - - const authService = getAuthService() - const user = await authService.getCurrentUser(request) - - if (!user && !isAnalyzeRequest) { - const anonymousQuota = await checkIpWindowRateLimit( - request, - RATE_LIMITS.applicationStarterAnonymousDaily, - ) - - if (!anonymousQuota.allowed) { - return new Response( - JSON.stringify({ - code: 'LOGIN_REQUIRED_FOR_MORE_GENERATIONS', - error: 'Anonymous generation limit reached', - limit: RATE_LIMITS.applicationStarterAnonymousDaily.limit, - loginRequired: true, - retryAfter: Math.ceil( - (anonymousQuota.resetAt.getTime() - Date.now()) / 1000, - ), - }), - { - status: 429, - headers: { - 'Content-Type': 'application/json', - ...Object.fromEntries(rateLimit.headers.entries()), - ...Object.fromEntries(anonymousQuota.headers.entries()), - }, - }, - ) - } - } - - const response = isAnalyzeRequest - ? await starterServer.analyzeApplicationStarterServer({ - data: body, - }) - : await starterServer.resolveApplicationStarterServer({ - data: body, - }) - - return new Response(JSON.stringify(response), { - headers: { - 'Cache-Control': 'no-store', - 'Content-Type': 'application/json', - ...Object.fromEntries(rateLimit.headers.entries()), - }, - }) - } catch (error) { - console.error('Error resolving application starter:', error) - return new Response( - JSON.stringify({ - error: 'Failed to resolve application starter', - }), - { - status: 500, - headers: { 'Content-Type': 'application/json' }, - }, - ) - } - }, + GET: ({ request }) => forwardWorkerRequest('STARTER', request), + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, }) diff --git a/src/routes/api/application-starter/suggest.ts b/src/routes/api/application-starter/suggest.ts index 08b04be22..67e2b36ba 100644 --- a/src/routes/api/application-starter/suggest.ts +++ b/src/routes/api/application-starter/suggest.ts @@ -1,44 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { - applicationStarterErrorResponse, - applicationStarterInternalErrorResponse, - applicationStarterJsonResponse, - readApplicationStarterJsonRequest, -} from '~/application-starter/api/request-boundary.server' -import { - applicationStarterSuggestBodySchema, - parseApplicationStarterRequest, -} from '~/application-starter/api/request-schema.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/application-starter/suggest')({ server: { handlers: { - POST: async ({ request }: { request: Request }) => { - try { - const requestBody = await readApplicationStarterJsonRequest(request) - if ('response' in requestBody) { - return requestBody.response - } - - let body - try { - body = parseApplicationStarterRequest(applicationStarterSuggestBodySchema, requestBody.body) - } catch { - return applicationStarterErrorResponse( - 'Invalid request body', - 400, - requestBody.rateLimit, - ) - } - - const { suggestHandler } = await import('~/application-starter/api/suggest') - const response = await suggestHandler(body) - return applicationStarterJsonResponse(response, requestBody.rateLimit) - } catch (error) { - console.error('Error suggesting features:', error) - return applicationStarterInternalErrorResponse('Failed to generate suggestions') - } - }, + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, }) diff --git a/src/routes/api/application-starter/validate.ts b/src/routes/api/application-starter/validate.ts index 11187445d..0c3d95dd4 100644 --- a/src/routes/api/application-starter/validate.ts +++ b/src/routes/api/application-starter/validate.ts @@ -1,47 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { - applicationStarterErrorResponse, - applicationStarterInternalErrorResponse, - applicationStarterJsonResponse, - readApplicationStarterJsonRequest, -} from '~/application-starter/api/request-boundary.server' -import { - applicationStarterValidateBodySchema, - parseApplicationStarterRequest, -} from '~/application-starter/api/request-schema.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/application-starter/validate')({ server: { handlers: { - POST: async ({ request }: { request: Request }) => { - try { - const requestBody = await readApplicationStarterJsonRequest(request) - if ('response' in requestBody) { - return requestBody.response - } - - let body - try { - body = parseApplicationStarterRequest( - applicationStarterValidateBodySchema, - requestBody.body, - ) - } catch { - return applicationStarterErrorResponse( - 'Invalid request body', - 400, - requestBody.rateLimit, - ) - } - - const { validateHandler } = await import('~/application-starter/api/validate') - const response = await validateHandler(body.definition) - return applicationStarterJsonResponse(response, requestBody.rateLimit) - } catch (error) { - console.error('Error validating project:', error) - return applicationStarterInternalErrorResponse('Failed to validate project') - } - }, + POST: ({ request }) => forwardWorkerRequest('STARTER', request), }, }, }) diff --git a/src/routes/api/chat/account/composer-drafts/$accountId.$scope.ts b/src/routes/api/chat/account/composer-drafts/$accountId.$scope.ts index 5d00de380..1680950d9 100644 --- a/src/routes/api/chat/account/composer-drafts/$accountId.$scope.ts +++ b/src/routes/api/chat/account/composer-drafts/$accountId.$scope.ts @@ -1,8 +1,13 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleComposerDraft } from '~/chat/server/composer-draft-http.server' -export const Route = createFileRoute('/api/chat/account/composer-drafts/$accountId/$scope')({ - server: { handlers: { - GET: ({ request, params }) => handleComposerDraft(request, params.accountId, params.scope), - POST: ({ request, params }) => handleComposerDraft(request, params.accountId, params.scope), - } }, +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/account/composer-drafts/$accountId/$scope', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/api/chat/account/devices.ts b/src/routes/api/chat/account/devices.ts index aeadef6c2..1a0ad8f4d 100644 --- a/src/routes/api/chat/account/devices.ts +++ b/src/routes/api/chat/account/devices.ts @@ -1,3 +1,11 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleDeviceAccount} from '~/chat/server/device-http.server' -export const Route=createFileRoute('/api/chat/account/devices')({server:{handlers:{GET:({request})=>handleDeviceAccount(request),POST:({request})=>handleDeviceAccount(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/account/devices')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/account/onboarding.ts b/src/routes/api/chat/account/onboarding.ts index d4ff3ac44..7b695f48c 100644 --- a/src/routes/api/chat/account/onboarding.ts +++ b/src/routes/api/chat/account/onboarding.ts @@ -1,5 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleOnboarding } from '~/chat/server/onboarding-http.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + export const Route = createFileRoute('/api/chat/account/onboarding')({ - server: { handlers: { GET: ({ request }) => handleOnboarding(request), POST: ({ request }) => handleOnboarding(request) } }, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/api/chat/account/preferences.ts b/src/routes/api/chat/account/preferences.ts index f77631d31..a071b17dc 100644 --- a/src/routes/api/chat/account/preferences.ts +++ b/src/routes/api/chat/account/preferences.ts @@ -1,5 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleAccountPreferences } from '~/chat/server/account-preferences-http.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + export const Route = createFileRoute('/api/chat/account/preferences')({ - server: { handlers: { GET: ({ request }) => handleAccountPreferences(request), POST: ({ request }) => handleAccountPreferences(request) } }, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/api/chat/activity.ts b/src/routes/api/chat/activity.ts index 883c835e3..8c98a4f78 100644 --- a/src/routes/api/chat/activity.ts +++ b/src/routes/api/chat/activity.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleWorkspaceActivity} from '~/chat/server/workspace-activity-http.server' -export const Route=createFileRoute('/api/chat/activity')({server:{handlers:{GET:({request})=>handleWorkspaceActivity(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/activity')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/app-version.ts b/src/routes/api/chat/app-version.ts index 63077f000..e031b382e 100644 --- a/src/routes/api/chat/app-version.ts +++ b/src/routes/api/chat/app-version.ts @@ -1,3 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { appBuildId } from '~/chat/core/app-version' -export const Route = createFileRoute('/api/chat/app-version')({ server: { handlers: { GET: () => Response.json({ build: appBuildId }, { headers: { 'Cache-Control': 'no-store' } }) } } }) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/app-version')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bootstrap.ts b/src/routes/api/chat/bootstrap.ts index f5829bb8a..ac2f42a73 100644 --- a/src/routes/api/chat/bootstrap.ts +++ b/src/routes/api/chat/bootstrap.ts @@ -1,3 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleBootstrap } from '~/chat/server/bootstrap-http.server' -export const Route = createFileRoute('/api/chat/bootstrap')({server:{handlers:{GET:({request})=>handleBootstrap(request)}}}) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bootstrap')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bot-drafts/$id.files.$fileId.content.ts b/src/routes/api/chat/bot-drafts/$id.files.$fileId.content.ts index b8c1f2dc9..4ce540b90 100644 --- a/src/routes/api/chat/bot-drafts/$id.files.$fileId.content.ts +++ b/src/routes/api/chat/bot-drafts/$id.files.$fileId.content.ts @@ -1,5 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/bot-drafts/$id/files/$fileId/content')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'bot-drafts', params.id, params.fileId, true) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/bot-drafts/$id/files/$fileId/content', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bot-drafts/$id.files.$fileId.ts b/src/routes/api/chat/bot-drafts/$id.files.$fileId.ts index c7798d1fc..1782e5708 100644 --- a/src/routes/api/chat/bot-drafts/$id.files.$fileId.ts +++ b/src/routes/api/chat/bot-drafts/$id.files.$fileId.ts @@ -1,8 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/bot-drafts/$id/files/$fileId')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'bot-drafts', params.id, params.fileId), -POST: ({request,params}) => handleFiles(request, 'bot-drafts', params.id, params.fileId), -PUT: ({request,params}) => handleFiles(request, 'bot-drafts', params.id, params.fileId), -DELETE: ({request,params}) => handleFiles(request, 'bot-drafts', params.id, params.fileId) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bot-drafts/$id/files/$fileId')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bot-drafts/$id.files.ts b/src/routes/api/chat/bot-drafts/$id.files.ts index d1d1daed2..54a838ac3 100644 --- a/src/routes/api/chat/bot-drafts/$id.files.ts +++ b/src/routes/api/chat/bot-drafts/$id.files.ts @@ -1,8 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/bot-drafts/$id/files')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'bot-drafts', params.id), -POST: ({request,params}) => handleFiles(request, 'bot-drafts', params.id), -PUT: ({request,params}) => handleFiles(request, 'bot-drafts', params.id), -DELETE: ({request,params}) => handleFiles(request, 'bot-drafts', params.id) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bot-drafts/$id/files')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bot-drafts/$id.ts b/src/routes/api/chat/bot-drafts/$id.ts index 027c48ec7..2ae91df3f 100644 --- a/src/routes/api/chat/bot-drafts/$id.ts +++ b/src/routes/api/chat/bot-drafts/$id.ts @@ -1,3 +1,11 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleBotDraft} from '~/chat/server/bot-draft-http.server' -export const Route=createFileRoute('/api/chat/bot-drafts/$id')({server:{handlers:{GET:({request,params})=>handleBotDraft(request,params.id),POST:({request,params})=>handleBotDraft(request,params.id)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bot-drafts/$id')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bots.ts b/src/routes/api/chat/bots.ts index a135beb01..6f501c15c 100644 --- a/src/routes/api/chat/bots.ts +++ b/src/routes/api/chat/bots.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleBotMutation} from '~/chat/server/bot-mutation-http.server' -export const Route=createFileRoute('/api/chat/bots')({server:{handlers:{POST:({request})=>handleBotMutation(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bots')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bots/$id.$operation.ts b/src/routes/api/chat/bots/$id.$operation.ts index 972b022ae..4974442fe 100644 --- a/src/routes/api/chat/bots/$id.$operation.ts +++ b/src/routes/api/chat/bots/$id.$operation.ts @@ -1,15 +1,12 @@ -import {handleConversationCopy} from '~/chat/server/conversation-copy-http.server' import { createFileRoute } from '@tanstack/react-router' -import { handleConversationRead, handleConversationSend } from '~/chat/server/conversation-http.server' - -import { handleBotMutation } from '~/chat/server/bot-mutation-http.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/bots/$id/$operation')({ server: { handlers: { - POST: ({ request, params }) => params.operation==='copies' ? handleConversationCopy(request,params.id,'bot') : ['move','delete','restore'].includes(params.operation) ? handleBotMutation(request,params.id,params.operation) : handleConversationSend(request, params.id, params.operation, 'bot'), - PATCH: ({request,params}) => handleBotMutation(request,params.id,params.operation), - GET: ({ request, params }) => params.operation==='copy-source' ? handleConversationCopy(request,params.id,'bot',true) : handleConversationRead(request, params.id, params.operation, 'bot'), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + PATCH: ({ request }) => forwardWorkerRequest('CHAT', request), + GET: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/bots/$id.files.$fileId.content.ts b/src/routes/api/chat/bots/$id.files.$fileId.content.ts index a2c02f7da..456f4965f 100644 --- a/src/routes/api/chat/bots/$id.files.$fileId.content.ts +++ b/src/routes/api/chat/bots/$id.files.$fileId.content.ts @@ -1,5 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/bots/$id/files/$fileId/content')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'bots', params.id, params.fileId, true) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/bots/$id/files/$fileId/content', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bots/$id.files.$fileId.ts b/src/routes/api/chat/bots/$id.files.$fileId.ts index 1f606bef7..9d7767f03 100644 --- a/src/routes/api/chat/bots/$id.files.$fileId.ts +++ b/src/routes/api/chat/bots/$id.files.$fileId.ts @@ -1,8 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/bots/$id/files/$fileId')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'bots', params.id, params.fileId), -POST: ({request,params}) => handleFiles(request, 'bots', params.id, params.fileId), -PUT: ({request,params}) => handleFiles(request, 'bots', params.id, params.fileId), -DELETE: ({request,params}) => handleFiles(request, 'bots', params.id, params.fileId) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bots/$id/files/$fileId')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bots/$id.files.ts b/src/routes/api/chat/bots/$id.files.ts index fbaca1a20..058b514f6 100644 --- a/src/routes/api/chat/bots/$id.files.ts +++ b/src/routes/api/chat/bots/$id.files.ts @@ -1,8 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/bots/$id/files')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'bots', params.id), -POST: ({request,params}) => handleFiles(request, 'bots', params.id), -PUT: ({request,params}) => handleFiles(request, 'bots', params.id), -DELETE: ({request,params}) => handleFiles(request, 'bots', params.id) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bots/$id/files')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bots/$id.ts b/src/routes/api/chat/bots/$id.ts index c887f0ca3..344cc2584 100644 --- a/src/routes/api/chat/bots/$id.ts +++ b/src/routes/api/chat/bots/$id.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleBotMutation} from '~/chat/server/bot-mutation-http.server' -export const Route=createFileRoute('/api/chat/bots/$id')({server:{handlers:{PATCH:({request,params})=>handleBotMutation(request,params.id)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bots/$id')({ + server: { + handlers: { + PATCH: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bots/bulk.ts b/src/routes/api/chat/bots/bulk.ts index b14ab0eae..540c184b5 100644 --- a/src/routes/api/chat/bots/bulk.ts +++ b/src/routes/api/chat/bots/bulk.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleBotMutation} from '~/chat/server/bot-mutation-http.server' -export const Route=createFileRoute('/api/chat/bots/bulk')({server:{handlers:{POST:({request})=>handleBotMutation(request,undefined,undefined,'bulk')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bots/bulk')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/bots/move.ts b/src/routes/api/chat/bots/move.ts index c2b30fe72..0c631fa83 100644 --- a/src/routes/api/chat/bots/move.ts +++ b/src/routes/api/chat/bots/move.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleBotMutation} from '~/chat/server/bot-mutation-http.server' -export const Route=createFileRoute('/api/chat/bots/move')({server:{handlers:{POST:({request})=>handleBotMutation(request,undefined,undefined,'move')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/bots/move')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/connection.ts b/src/routes/api/chat/connection.ts index 18ff8f6ad..ab394ffcc 100644 --- a/src/routes/api/chat/connection.ts +++ b/src/routes/api/chat/connection.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleProviderConnection} from '~/chat/server/provider-connection-http.server' -export const Route=createFileRoute('/api/chat/connection')({server:{handlers:{POST:({request})=>handleProviderConnection(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/connection')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id.$operation.ts b/src/routes/api/chat/conversations/$id.$operation.ts index 4e1201612..5da4605c7 100644 --- a/src/routes/api/chat/conversations/$id.$operation.ts +++ b/src/routes/api/chat/conversations/$id.$operation.ts @@ -1,19 +1,12 @@ -import {handleConversationRetry} from '~/chat/server/conversation-retry-http.server' -import {handleConversationCopy} from '~/chat/server/conversation-copy-http.server' -import {handleWorkflowRun} from '~/chat/server/workflow-run-http.server' -import {handleWorkflow} from '~/chat/server/workflow-http.server' -import {handleConversationExecution} from '~/chat/server/conversation-execution-http.server' import { createFileRoute } from '@tanstack/react-router' -import { handleConversationRead, handleConversationSend } from '~/chat/server/conversation-http.server' - -import {handleConversationThreads} from '~/chat/server/conversation-threads-http.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/conversations/$id/$operation')({ server: { handlers: { - POST: ({ request, params }) => params.operation==='retries' ? handleConversationRetry(request,params.id,'create') : params.operation==='copies' ? handleConversationCopy(request,params.id,'conversation') : params.operation==='workflow-runs' ? handleWorkflowRun(request,params.id) : params.operation==='workflows' ? handleWorkflow(request,params.id) : params.operation==='execution' ? handleConversationExecution(request,params.id) : ['thread','threads'].includes(params.operation) ? handleConversationThreads(request,params.id,params.operation) : handleConversationSend(request, params.id, params.operation), - DELETE: ({request,params}) => handleConversationThreads(request,params.id,params.operation), - GET: ({ request, params }) => params.operation==='copy-source' ? handleConversationCopy(request,params.id,'conversation',true) : params.operation==='workflow-runs' ? handleWorkflowRun(request,params.id) : params.operation==='workflows' ? handleWorkflow(request,params.id) : params.operation==='execution' ? handleConversationExecution(request,params.id) : ['thread','threads'].includes(params.operation) ? handleConversationThreads(request,params.id,params.operation) : handleConversationRead(request, params.id, params.operation), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + GET: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts b/src/routes/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts index 8fdf248e0..b998c1955 100644 --- a/src/routes/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts +++ b/src/routes/api/chat/conversations/$id.execution.snapshots.$snapshotId.ts @@ -1,6 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleConversationExecution} from '~/chat/server/conversation-execution-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/execution/snapshots/$snapshotId')({server:{handlers:{ - GET:({request,params})=>handleConversationExecution(request,params.id,params.snapshotId), - PUT:({request,params})=>handleConversationExecution(request,params.id,params.snapshotId), -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/execution/snapshots/$snapshotId', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id.files.$fileId.content.ts b/src/routes/api/chat/conversations/$id.files.$fileId.content.ts index dce31ee24..2497c0752 100644 --- a/src/routes/api/chat/conversations/$id.files.$fileId.content.ts +++ b/src/routes/api/chat/conversations/$id.files.$fileId.content.ts @@ -1,5 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/files/$fileId/content')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'conversations', params.id, params.fileId, true) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/files/$fileId/content', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id.files.$fileId.ts b/src/routes/api/chat/conversations/$id.files.$fileId.ts index c1806bbb0..fd827ab8b 100644 --- a/src/routes/api/chat/conversations/$id.files.$fileId.ts +++ b/src/routes/api/chat/conversations/$id.files.$fileId.ts @@ -1,8 +1,15 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/files/$fileId')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'conversations', params.id, params.fileId), -POST: ({request,params}) => handleFiles(request, 'conversations', params.id, params.fileId), -PUT: ({request,params}) => handleFiles(request, 'conversations', params.id, params.fileId), -DELETE: ({request,params}) => handleFiles(request, 'conversations', params.id, params.fileId) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/files/$fileId', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id.files.ts b/src/routes/api/chat/conversations/$id.files.ts index 0be2b15f5..67e809897 100644 --- a/src/routes/api/chat/conversations/$id.files.ts +++ b/src/routes/api/chat/conversations/$id.files.ts @@ -1,8 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFiles} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/files')({server:{handlers:{ -GET: ({request,params}) => handleFiles(request, 'conversations', params.id), -POST: ({request,params}) => handleFiles(request, 'conversations', params.id), -PUT: ({request,params}) => handleFiles(request, 'conversations', params.id), -DELETE: ({request,params}) => handleFiles(request, 'conversations', params.id) -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/conversations/$id/files')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts b/src/routes/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts index eee00e580..0833c782c 100644 --- a/src/routes/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts +++ b/src/routes/api/chat/conversations/$id.workflow-runs.$runId.cancel.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleWorkflowRun} from '~/chat/server/workflow-run-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/workflow-runs/$runId/cancel')({server:{handlers:{POST:({request,params})=>handleWorkflowRun(request,params.id,params.runId,true)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/workflow-runs/$runId/cancel', +)({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id.workflow-runs.$runId.ts b/src/routes/api/chat/conversations/$id.workflow-runs.$runId.ts index bded70607..3517f9233 100644 --- a/src/routes/api/chat/conversations/$id.workflow-runs.$runId.ts +++ b/src/routes/api/chat/conversations/$id.workflow-runs.$runId.ts @@ -1,6 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleWorkflowRun} from '~/chat/server/workflow-run-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/workflow-runs/$runId')({server:{handlers:{ -GET:({request,params})=>handleWorkflowRun(request,params.id,params.runId), -POST:({request,params})=>handleWorkflowRun(request,params.id,params.runId), -}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/workflow-runs/$runId', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id.workflows.$workflowId.ts b/src/routes/api/chat/conversations/$id.workflows.$workflowId.ts index 3b379a66e..ce035cf89 100644 --- a/src/routes/api/chat/conversations/$id.workflows.$workflowId.ts +++ b/src/routes/api/chat/conversations/$id.workflows.$workflowId.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleWorkflow} from '~/chat/server/workflow-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/workflows/$workflowId')({server:{handlers:{GET:({request,params})=>handleWorkflow(request,params.id,params.workflowId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/workflows/$workflowId', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id/files/import.ts b/src/routes/api/chat/conversations/$id/files/import.ts index c63b839c6..c68b5ef9c 100644 --- a/src/routes/api/chat/conversations/$id/files/import.ts +++ b/src/routes/api/chat/conversations/$id/files/import.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleFileImport} from '~/chat/server/file-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/files/import')({server:{handlers:{POST:({request,params})=>handleFileImport(request,params.id)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/files/import', +)({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id/memories.ts b/src/routes/api/chat/conversations/$id/memories.ts index d420e1a05..121ead735 100644 --- a/src/routes/api/chat/conversations/$id/memories.ts +++ b/src/routes/api/chat/conversations/$id/memories.ts @@ -1,3 +1,11 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleMemory} from '~/chat/server/memory-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/memories')({server:{handlers:{GET:({request,params})=>handleMemory(request,params.id),POST:({request,params})=>handleMemory(request,params.id)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/conversations/$id/memories')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id/memories/$memoryId.ts b/src/routes/api/chat/conversations/$id/memories/$memoryId.ts index 30830c3d7..e26d80bcc 100644 --- a/src/routes/api/chat/conversations/$id/memories/$memoryId.ts +++ b/src/routes/api/chat/conversations/$id/memories/$memoryId.ts @@ -1,3 +1,13 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleMemory} from '~/chat/server/memory-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/memories/$memoryId')({server:{handlers:{GET:({request,params})=>handleMemory(request,params.id,params.memoryId),POST:({request,params})=>handleMemory(request,params.id,params.memoryId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/conversations/$id/memories/$memoryId', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/conversations/$id/references.ts b/src/routes/api/chat/conversations/$id/references.ts index f4f4069b9..2857e6af9 100644 --- a/src/routes/api/chat/conversations/$id/references.ts +++ b/src/routes/api/chat/conversations/$id/references.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleReferences} from '~/chat/server/reference-http.server' -export const Route=createFileRoute('/api/chat/conversations/$id/references')({server:{handlers:{GET:({request,params})=>handleReferences(request,params.id)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/conversations/$id/references')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/copies/$id.ts b/src/routes/api/chat/copies/$id.ts index 77c3a7509..145314da2 100644 --- a/src/routes/api/chat/copies/$id.ts +++ b/src/routes/api/chat/copies/$id.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleConversationCopy} from '~/chat/server/conversation-copy-http.server' -export const Route=createFileRoute('/api/chat/copies/$id')({server:{handlers:{GET:({request,params})=>handleConversationCopy(request,params.id,'operation')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/copies/$id')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/device-transport.ts b/src/routes/api/chat/device-transport.ts index dd46a2e97..9e093977c 100644 --- a/src/routes/api/chat/device-transport.ts +++ b/src/routes/api/chat/device-transport.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleDeviceTransport} from '~/chat/server/device-http.server' -export const Route=createFileRoute('/api/chat/device-transport')({server:{handlers:{POST:({request})=>handleDeviceTransport(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/device-transport')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/draft.ts b/src/routes/api/chat/draft.ts index 2df67f3a8..4091797cd 100644 --- a/src/routes/api/chat/draft.ts +++ b/src/routes/api/chat/draft.ts @@ -1,34 +1,11 @@ -import { hasChatAccess } from '~/chat/access.server' -import { z } from 'zod' import { createFileRoute } from '@tanstack/react-router' -import { getAuthService } from '~/auth/index.server' -import { jsonError, readJsonBody, validateSameOriginRequest } from '~/utils/api-boundary.server' -import { composerDraftApi } from '~/chat/server/composer-drafts' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' -async function handle(request: Request) { - if (request.method === 'POST') { - const rejection = validateSameOriginRequest(request) - if (rejection) return jsonError(rejection.message, rejection.status) - } - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Sign in to continue.', 401) - if (!await hasChatAccess(user)) - return jsonError('Chat access is unavailable.', 403) - const scope = new URL(request.url).searchParams.get('scope') ?? '' - const body = request.method === 'POST' - ? await readJsonBody(request, { maxContentLength: 600_000 }) - : undefined - if (body && !body.success) return jsonError(body.error.message, body.error.status) - try { - return await composerDraftApi(request, user.userId, scope, body?.success ? body.body : undefined) - } catch (error) { - if (error instanceof z.ZodError) return jsonError('Invalid draft request.', 400) - throw error - } -} export const Route = createFileRoute('/api/chat/draft')({ - server: { handlers: { - GET: ({ request }) => handle(request), - POST: ({ request }) => handle(request), - } }, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/api/chat/files.ts b/src/routes/api/chat/files.ts index 3dedc2165..5fb0cfc6e 100644 --- a/src/routes/api/chat/files.ts +++ b/src/routes/api/chat/files.ts @@ -1,91 +1,11 @@ -import { hasChatAccess } from '~/chat/access.server' -import { z } from 'zod' -import type { FileScope, DraftFileScope } from '~/chat/core/files' import { createFileRoute } from '@tanstack/react-router' -import { getAuthService } from '~/auth/index.server' -import { - jsonError, - validateSameOriginRequest, -} from '~/utils/api-boundary.server' -import { fileApi } from '~/chat/server/file-api' -import { getFileEnvironment } from '~/chat/server/file-environment.server' -import { SavedFileError } from '~/chat/server/saved-file-contract' -const requestSchema = z - .object({ - workspaceId: z.string().min(1).max(1000), - botId: z.string().min(1).max(1000).optional(), - conversationId: z.string().min(1).max(1000).optional(), - draftId: z.string().uuid().optional(), - id: z.string().uuid().optional(), - content: z.enum(['0', '1']).default('0'), - }) - .superRefine((value, ctx) => { - if ( - value.draftId - ? Boolean(value.botId || value.conversationId) - : !value.botId - ) - ctx.addIssue({ - code: 'custom', - message: 'Choose a draft or a conversation.', - }) - if (value.content === '1' && !value.id) - ctx.addIssue({ code: 'custom', message: 'Choose a file.' }) - }) -export async function handleFileRequest(request: Request) { - if (request.method === 'PUT') { - const rejection = validateSameOriginRequest(request) - if (rejection) return jsonError(rejection.message, rejection.status) - } - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Sign in to continue.', 401) - if ( - !await hasChatAccess(user) - ) - return jsonError('Chat access is unavailable.', 403) - const parsed = requestSchema.safeParse( - Object.fromEntries(new URL(request.url).searchParams), - ) - if (!parsed.success) return jsonError('Invalid file request.', 400) - const value = parsed.data - let scope: FileScope | DraftFileScope - if (value.draftId) - scope = { - workspaceId: value.workspaceId, - userId: user.userId, - draftId: value.draftId, - } - else { - if (!value.botId) return jsonError('Invalid file request.', 400) - scope = { - workspaceId: value.workspaceId, - userId: user.userId, - botId: value.botId, - conversationId: value.conversationId, - } - } +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' - try { - return await fileApi( - request, - await getFileEnvironment(), - scope, - value.id, - value.content === '1', - ) - } catch (error) { - if (error instanceof SavedFileError) - return jsonError(error.message, error.status) - if (error instanceof z.ZodError) - return jsonError('Invalid file request.', 400) - throw error - } -} export const Route = createFileRoute('/api/chat/files')({ server: { handlers: { - GET: ({ request }) => handleFileRequest(request), - PUT: ({ request }) => handleFileRequest(request), + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + PUT: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/kody/account.ts b/src/routes/api/chat/kody/account.ts index 69476253b..8364be391 100644 --- a/src/routes/api/chat/kody/account.ts +++ b/src/routes/api/chat/kody/account.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/account')({server:{handlers:{GET:({request})=>handleKodyAccount(request,'account')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/account')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/callback.ts b/src/routes/api/chat/kody/callback.ts index b0f886f9f..f9bf6c0fa 100644 --- a/src/routes/api/chat/kody/callback.ts +++ b/src/routes/api/chat/kody/callback.ts @@ -1,3 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleKodyCallback } from '~/chat/server/kody-callback-http.server' -export const Route = createFileRoute('/api/chat/kody/callback')({ server: { handlers: { GET: ({ request }) => handleKodyCallback(request) } } }) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/callback')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/community.ts b/src/routes/api/chat/kody/community.ts index ab7bf52d1..1bca81205 100644 --- a/src/routes/api/chat/kody/community.ts +++ b/src/routes/api/chat/kody/community.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/community')({server:{handlers:{GET:({request})=>handleKodyAccount(request,'community')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/community')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/community/$listingId.ts b/src/routes/api/chat/kody/community/$listingId.ts index a6b52b8f0..093d812f5 100644 --- a/src/routes/api/chat/kody/community/$listingId.ts +++ b/src/routes/api/chat/kody/community/$listingId.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/community/$listingId')({server:{handlers:{GET:({request,params})=>handleKodyAccount(request,'community',params.listingId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/community/$listingId')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/connect.ts b/src/routes/api/chat/kody/connect.ts index 37c90b9e5..4eb5d717b 100644 --- a/src/routes/api/chat/kody/connect.ts +++ b/src/routes/api/chat/kody/connect.ts @@ -1,3 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleKodyConnect } from '~/chat/server/kody-connect-http.server' -export const Route = createFileRoute('/api/chat/kody/connect')({ server: { handlers: { POST: ({ request }) => handleKodyConnect(request) } } }) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/connect')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/jobs/$jobId/enabled.ts b/src/routes/api/chat/kody/jobs/$jobId/enabled.ts index 3b5eaea88..ce8e20dcb 100644 --- a/src/routes/api/chat/kody/jobs/$jobId/enabled.ts +++ b/src/routes/api/chat/kody/jobs/$jobId/enabled.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyControl} from '~/chat/server/kody-control-http.server' -export const Route=createFileRoute('/api/chat/kody/jobs/$jobId/enabled')({server:{handlers:{POST:({request,params})=>handleKodyControl(request,'job-enabled',params.jobId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/jobs/$jobId/enabled')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/mail/inboxes.ts b/src/routes/api/chat/kody/mail/inboxes.ts index b98a1f62f..5af1382bd 100644 --- a/src/routes/api/chat/kody/mail/inboxes.ts +++ b/src/routes/api/chat/kody/mail/inboxes.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/mail/inboxes')({server:{handlers:{GET:({request})=>handleKodyAccount(request,'mail-inboxes')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/mail/inboxes')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/mail/messages.ts b/src/routes/api/chat/kody/mail/messages.ts index 3818894e0..d771aa4a8 100644 --- a/src/routes/api/chat/kody/mail/messages.ts +++ b/src/routes/api/chat/kody/mail/messages.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/mail/messages')({server:{handlers:{GET:({request})=>handleKodyAccount(request,'mail-messages')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/mail/messages')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/mail/messages/$messageId.ts b/src/routes/api/chat/kody/mail/messages/$messageId.ts index 5813ba23b..42d82e67a 100644 --- a/src/routes/api/chat/kody/mail/messages/$messageId.ts +++ b/src/routes/api/chat/kody/mail/messages/$messageId.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/mail/messages/$messageId')({server:{handlers:{GET:({request,params})=>handleKodyAccount(request,'mail-messages',params.messageId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/mail/messages/$messageId')( + { + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, + }, +) diff --git a/src/routes/api/chat/kody/memories.ts b/src/routes/api/chat/kody/memories.ts index a1b2758f3..cdc0693da 100644 --- a/src/routes/api/chat/kody/memories.ts +++ b/src/routes/api/chat/kody/memories.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/memories')({server:{handlers:{GET:({request})=>handleKodyAccount(request,'memories')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/memories')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/memories/$memoryId.ts b/src/routes/api/chat/kody/memories/$memoryId.ts index e6cd4718e..31660c8a0 100644 --- a/src/routes/api/chat/kody/memories/$memoryId.ts +++ b/src/routes/api/chat/kody/memories/$memoryId.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/memories/$memoryId')({server:{handlers:{GET:({request,params})=>handleKodyAccount(request,'memories',params.memoryId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/memories/$memoryId')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/memories/$memoryId/apply.ts b/src/routes/api/chat/kody/memories/$memoryId/apply.ts index 87ac3e4cf..d65c164e5 100644 --- a/src/routes/api/chat/kody/memories/$memoryId/apply.ts +++ b/src/routes/api/chat/kody/memories/$memoryId/apply.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyMemoryMutation} from '~/chat/server/kody-memory-http.server' -export const Route=createFileRoute('/api/chat/kody/memories/$memoryId/apply')({server:{handlers:{POST:({request,params})=>handleKodyMemoryMutation(request,'apply',params.memoryId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/memories/$memoryId/apply')( + { + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, + }, +) diff --git a/src/routes/api/chat/kody/memories/$memoryId/review.ts b/src/routes/api/chat/kody/memories/$memoryId/review.ts index f7991eaff..c7bec624d 100644 --- a/src/routes/api/chat/kody/memories/$memoryId/review.ts +++ b/src/routes/api/chat/kody/memories/$memoryId/review.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyMemoryMutation} from '~/chat/server/kody-memory-http.server' -export const Route=createFileRoute('/api/chat/kody/memories/$memoryId/review')({server:{handlers:{POST:({request,params})=>handleKodyMemoryMutation(request,'review',params.memoryId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/kody/memories/$memoryId/review', +)({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/memories/create/apply.ts b/src/routes/api/chat/kody/memories/create/apply.ts index 2015a280c..3400964f6 100644 --- a/src/routes/api/chat/kody/memories/create/apply.ts +++ b/src/routes/api/chat/kody/memories/create/apply.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyMemoryMutation} from '~/chat/server/kody-memory-http.server' -export const Route=createFileRoute('/api/chat/kody/memories/create/apply')({server:{handlers:{POST:({request})=>handleKodyMemoryMutation(request,'apply')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/memories/create/apply')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/memories/create/review.ts b/src/routes/api/chat/kody/memories/create/review.ts index c6e54ea7b..1a1afffe9 100644 --- a/src/routes/api/chat/kody/memories/create/review.ts +++ b/src/routes/api/chat/kody/memories/create/review.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyMemoryMutation} from '~/chat/server/kody-memory-http.server' -export const Route=createFileRoute('/api/chat/kody/memories/create/review')({server:{handlers:{POST:({request})=>handleKodyMemoryMutation(request,'review')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/memories/create/review')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/packages/$packageId/document.ts b/src/routes/api/chat/kody/packages/$packageId/document.ts index 071cf7fdb..36b275781 100644 --- a/src/routes/api/chat/kody/packages/$packageId/document.ts +++ b/src/routes/api/chat/kody/packages/$packageId/document.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/packages/$packageId/document')({server:{handlers:{GET:({request,params})=>handleKodyAccount(request,'package-document',params.packageId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/kody/packages/$packageId/document', +)({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/resources/$kind.ts b/src/routes/api/chat/kody/resources/$kind.ts index 8109f8ce6..a4ae4974c 100644 --- a/src/routes/api/chat/kody/resources/$kind.ts +++ b/src/routes/api/chat/kody/resources/$kind.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/resources/$kind')({server:{handlers:{GET:({request,params})=>handleKodyAccount(request,'resources',params.kind)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/resources/$kind')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/runs.ts b/src/routes/api/chat/kody/runs.ts index fc9d7babe..54030cf4e 100644 --- a/src/routes/api/chat/kody/runs.ts +++ b/src/routes/api/chat/kody/runs.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/runs')({server:{handlers:{GET:({request})=>handleKodyAccount(request,'runs')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/runs')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/runs/$runId.ts b/src/routes/api/chat/kody/runs/$runId.ts index cdeab0470..ed8e8e446 100644 --- a/src/routes/api/chat/kody/runs/$runId.ts +++ b/src/routes/api/chat/kody/runs/$runId.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/runs/$runId')({server:{handlers:{GET:({request,params})=>handleKodyAccount(request,'runs',params.runId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/runs/$runId')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/runs/$runId/triage.ts b/src/routes/api/chat/kody/runs/$runId/triage.ts index f6f23edba..bf5ace45d 100644 --- a/src/routes/api/chat/kody/runs/$runId/triage.ts +++ b/src/routes/api/chat/kody/runs/$runId/triage.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyControl} from '~/chat/server/kody-control-http.server' -export const Route=createFileRoute('/api/chat/kody/runs/$runId/triage')({server:{handlers:{POST:({request,params})=>handleKodyControl(request,'run-triage',params.runId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/runs/$runId/triage')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/servers.ts b/src/routes/api/chat/kody/servers.ts index 5d3ecf3d7..b64756602 100644 --- a/src/routes/api/chat/kody/servers.ts +++ b/src/routes/api/chat/kody/servers.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyControl} from '~/chat/server/kody-control-http.server' -export const Route=createFileRoute('/api/chat/kody/servers')({server:{handlers:{POST:({request})=>handleKodyControl(request,'server-add')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/servers')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/servers/$serverId/check.ts b/src/routes/api/chat/kody/servers/$serverId/check.ts index e69cdbd82..e4aec94d8 100644 --- a/src/routes/api/chat/kody/servers/$serverId/check.ts +++ b/src/routes/api/chat/kody/servers/$serverId/check.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyControl} from '~/chat/server/kody-control-http.server' -export const Route=createFileRoute('/api/chat/kody/servers/$serverId/check')({server:{handlers:{POST:({request,params})=>handleKodyControl(request,'server-check',params.serverId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/servers/$serverId/check')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/servers/$serverId/enabled.ts b/src/routes/api/chat/kody/servers/$serverId/enabled.ts index e42f2cc5e..9bdf27cba 100644 --- a/src/routes/api/chat/kody/servers/$serverId/enabled.ts +++ b/src/routes/api/chat/kody/servers/$serverId/enabled.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyControl} from '~/chat/server/kody-control-http.server' -export const Route=createFileRoute('/api/chat/kody/servers/$serverId/enabled')({server:{handlers:{POST:({request,params})=>handleKodyControl(request,'server-enabled',params.serverId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/kody/servers/$serverId/enabled', +)({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/servers/$serverId/reconnect.ts b/src/routes/api/chat/kody/servers/$serverId/reconnect.ts index 59c11c125..11bb4c398 100644 --- a/src/routes/api/chat/kody/servers/$serverId/reconnect.ts +++ b/src/routes/api/chat/kody/servers/$serverId/reconnect.ts @@ -1,3 +1,12 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyControl} from '~/chat/server/kody-control-http.server' -export const Route=createFileRoute('/api/chat/kody/servers/$serverId/reconnect')({server:{handlers:{POST:({request,params})=>handleKodyControl(request,'server-reconnect',params.serverId)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute( + '/api/chat/kody/servers/$serverId/reconnect', +)({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/sync.ts b/src/routes/api/chat/kody/sync.ts index 141c4a497..446ebdf5e 100644 --- a/src/routes/api/chat/kody/sync.ts +++ b/src/routes/api/chat/kody/sync.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/sync')({server:{handlers:{POST:({request})=>handleKodyAccount(request,'sync')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/sync')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/unlink.ts b/src/routes/api/chat/kody/unlink.ts index 1c078e31d..6a2222e9f 100644 --- a/src/routes/api/chat/kody/unlink.ts +++ b/src/routes/api/chat/kody/unlink.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyUnlink} from '~/chat/server/kody-unlink-http.server' -export const Route=createFileRoute('/api/chat/kody/unlink')({server:{handlers:{POST:({request})=>handleKodyUnlink(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/unlink')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/kody/usage.ts b/src/routes/api/chat/kody/usage.ts index 4739fea73..0672d7d27 100644 --- a/src/routes/api/chat/kody/usage.ts +++ b/src/routes/api/chat/kody/usage.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleKodyAccount} from '~/chat/server/kody-account-http.server' -export const Route=createFileRoute('/api/chat/kody/usage')({server:{handlers:{GET:({request})=>handleKodyAccount(request,'usage')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/kody/usage')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/mcp/$.ts b/src/routes/api/chat/mcp/$.ts index 8af1c5923..c27121fc9 100644 --- a/src/routes/api/chat/mcp/$.ts +++ b/src/routes/api/chat/mcp/$.ts @@ -1,32 +1,11 @@ -import { hasChatAccess } from '~/chat/access.server' -import { z } from 'zod' import { createFileRoute } from '@tanstack/react-router' -import { getAuthService } from '~/auth/index.server' -import { jsonError, validateSameOriginRequest } from '~/utils/api-boundary.server' -import { readWorkspacePolicy, WorkspacePolicyError } from '~/chat/workspace-policy.server' -import { getMcpEnvironment, McpEnvironmentError } from '~/chat/server/mcp-environment.server' -import { mcpAccountApi } from '~/chat/server/mcp-account-api' -import { McpAccountError } from '~/chat/server/mcp-account-contract' -import { McpAuthorizationError } from '~/chat/server/mcp-auth-network' -export async function handleMcp(request: Request) { - if(request.method==='POST') { - const error=validateSameOriginRequest(request) - if(error) return jsonError(error.message,error.status) - } - const user=await getAuthService().getCurrentUser(request) - if(!user) return jsonError('Sign in to continue.',401) - if(!await hasChatAccess(user)) return jsonError('Chat access is unavailable.',403) - const url=new URL(request.url) - const workspace=z.string().min(1).max(1000).safeParse(url.searchParams.get('workspaceId')) - if(!workspace.success) return jsonError('Choose a workspace.',400) - try { - await readWorkspacePolicy(workspace.data,user.userId) - const path=url.pathname.slice('/api/chat/'.length) - return await mcpAccountApi(request,await getMcpEnvironment(),{workspaceId:workspace.data,userId:user.userId},path)??jsonError('Not found.',404) - } catch(error) { - if(error instanceof WorkspacePolicyError||error instanceof McpEnvironmentError||error instanceof McpAccountError||error instanceof McpAuthorizationError) return jsonError(error.message,error.status) - if(error instanceof z.ZodError) return jsonError('The connection request is invalid.',400) - throw error - } -} -export const Route=createFileRoute('/api/chat/mcp/$')({server:{handlers:{GET:({request})=>handleMcp(request),POST:({request})=>handleMcp(request)}}}) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/mcp/$')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/mcp/contract.ts b/src/routes/api/chat/mcp/contract.ts index b15a5b236..2af2763ea 100644 --- a/src/routes/api/chat/mcp/contract.ts +++ b/src/routes/api/chat/mcp/contract.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleReferenceCatalog} from '~/chat/server/reference-catalog-http.server' -export const Route=createFileRoute('/api/chat/mcp/contract')({server:{handlers:{GET:({request})=>handleReferenceCatalog(request,'mcp-contract')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/mcp/contract')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/model-options.ts b/src/routes/api/chat/model-options.ts index e1a370c4c..eb46ee1e1 100644 --- a/src/routes/api/chat/model-options.ts +++ b/src/routes/api/chat/model-options.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleModelOptions} from '~/chat/server/model-options-http.server' -export const Route=createFileRoute('/api/chat/model-options')({server:{handlers:{GET:({request})=>handleModelOptions(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/model-options')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/models.ts b/src/routes/api/chat/models.ts index 4af5f653c..15f227632 100644 --- a/src/routes/api/chat/models.ts +++ b/src/routes/api/chat/models.ts @@ -1,43 +1,10 @@ -import { hasChatAccess } from '~/chat/access.server' -import { z } from 'zod' import { createFileRoute } from '@tanstack/react-router' -import { getAuthService } from '~/auth/index.server' -import { jsonError, jsonResponse } from '~/utils/api-boundary.server' -import { - readWorkspacePolicy, - WorkspacePolicyError, -} from '~/chat/workspace-policy.server' -import { getModelEnvironment } from '~/chat/server/model-environment.server' -import { getRunModelCatalog, RunModelError } from '~/chat/server/run-models' -const workspaceIdSchema = z.string().min(1).max(1000) -export async function handleModelCatalog(request: Request) { - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Sign in to continue.', 401) - if ( - !await hasChatAccess(user) - ) - return jsonError('Chat access is unavailable.', 403) - const parsed = workspaceIdSchema.safeParse( - new URL(request.url).searchParams.get('workspaceId'), - ) - if (!parsed.success) return jsonError('Choose a workspace.', 400) - try { - const policy = await readWorkspacePolicy(parsed.data, user.userId) - const env = await getModelEnvironment() - const catalog = await getRunModelCatalog(env, { - userId: user.userId, - policy, - fixture: false, - }) - return jsonResponse(catalog, { - headers: { 'Cache-Control': 'private, no-store' }, - }) - } catch (error) { - if (error instanceof WorkspacePolicyError || error instanceof RunModelError) - return jsonError(error.message, error.status) - throw error - } -} +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + export const Route = createFileRoute('/api/chat/models')({ - server: { handlers: { GET: ({ request }) => handleModelCatalog(request) } }, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/api/chat/openrouter/callback.ts b/src/routes/api/chat/openrouter/callback.ts index 93936afcb..592b81965 100644 --- a/src/routes/api/chat/openrouter/callback.ts +++ b/src/routes/api/chat/openrouter/callback.ts @@ -1,36 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/openrouter/callback')({ - server: { handlers: { GET: () => openRouterCallbackResponse() } }, -}) - -export function openRouterCallbackResponse() { - const nonce = crypto.randomUUID() - // Do not load the app, analytics, or third-party assets while the - // authorization code is in the URL. - return new Response(` -Connect OpenRouter | TanChat -

Return to Builder to finish connecting OpenRouter.

-`, { - headers: { - 'Content-Type': 'text/html; charset=utf-8', - 'Cache-Control': 'no-store', - 'Referrer-Policy': 'no-referrer', - 'Content-Security-Policy': `default-src 'none'; script-src 'nonce-${nonce}'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'`, - 'X-Content-Type-Options': 'nosniff', - 'Cross-Origin-Opener-Policy': 'same-origin', + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), }, - }) -} + }, +}) diff --git a/src/routes/api/chat/plugins.ts b/src/routes/api/chat/plugins.ts index 7a3d81254..9b30c22c2 100644 --- a/src/routes/api/chat/plugins.ts +++ b/src/routes/api/chat/plugins.ts @@ -1,28 +1,11 @@ -import { hasChatAccess } from '~/chat/access.server' -import { z } from 'zod' import { createFileRoute } from '@tanstack/react-router' -import { getAuthService } from '~/auth/index.server' -import { jsonError,validateSameOriginRequest } from '~/utils/api-boundary.server' -import { readWorkspacePolicy,WorkspacePolicyError } from '~/chat/workspace-policy.server' -import { getKodyEnvironment,KodyEnvironmentError } from '~/chat/server/kody-environment.server' -import { pluginApi } from '~/chat/server/plugin-api' -export async function handlePlugins(request:Request, id?:string) { - if(request.method==='POST') { - const error=validateSameOriginRequest(request) - if(error) return jsonError(error.message,error.status) - } - const user=await getAuthService().getCurrentUser(request) - if(!user) return jsonError('Sign in to continue.',401) - if(!await hasChatAccess(user)) return jsonError('Chat access is unavailable.',403) - const url=new URL(request.url) - const parsed=z.string().min(1).max(1000).safeParse(url.searchParams.get('workspaceId')) - if(url.searchParams.getAll('workspaceId').length!==1||!parsed.success) return jsonError('Choose a workspace.',400) - try { - await readWorkspacePolicy(parsed.data,user.userId) - return await pluginApi(request,await getKodyEnvironment(),{workspaceId:parsed.data,userId:user.userId},id??url.searchParams.get('id')??undefined) - } catch(error) { - if(error instanceof WorkspacePolicyError||error instanceof KodyEnvironmentError) return jsonError(error.message,error.status) - throw error - } -} -export const Route=createFileRoute('/api/chat/plugins')({server:{handlers:{GET:({request})=>handlePlugins(request),POST:({request})=>handlePlugins(request)}}}) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/plugins')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/plugins/$id.ts b/src/routes/api/chat/plugins/$id.ts index 8846d3468..5223a7704 100644 --- a/src/routes/api/chat/plugins/$id.ts +++ b/src/routes/api/chat/plugins/$id.ts @@ -1,11 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { handlePlugins } from '../plugins' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/plugins/$id')({ server: { handlers: { - GET: ({ request, params }) => handlePlugins(request, params.id), - POST: ({ request, params }) => handlePlugins(request, params.id), + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/policy.ts b/src/routes/api/chat/policy.ts index bac151812..4007290da 100644 --- a/src/routes/api/chat/policy.ts +++ b/src/routes/api/chat/policy.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleWorkspacePolicy} from '~/chat/server/workspace-policy-http.server' -export const Route=createFileRoute('/api/chat/policy')({server:{handlers:{POST:({request})=>handleWorkspacePolicy(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/policy')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/project-snapshots.$hash.quarantine.ts b/src/routes/api/chat/project-snapshots.$hash.quarantine.ts index f7ce51c99..dd1509d61 100644 --- a/src/routes/api/chat/project-snapshots.$hash.quarantine.ts +++ b/src/routes/api/chat/project-snapshots.$hash.quarantine.ts @@ -1,172 +1,12 @@ import { createFileRoute } from '@tanstack/react-router' -import { - jsonError, - jsonResponse, - validateSameOriginRequest, -} from '~/utils/api-boundary.server' -import { - purgeHostingCacheTags, - type PurgeResult, -} from '~/utils/hosting-cache.server' -import { - getBuilderProjectSnapshotCacheTag, - isBuilderProjectSnapshotHash, - isBuilderProjectSnapshotQuarantined, - BuilderProjectSnapshotStorageUnavailableError, - quarantineBuilderProjectSnapshot, -} from '~/utils/builder-project-snapshot-storage.server' -import { - BuilderProjectStorageUnavailableError, - quarantineStoredBuilderProjectsBySnapshotHash, -} from '~/utils/builder-project-storage.server' -import { quarantineBuilderProjectsBySnapshotHash } from '~/utils/builder-project-events.server' - -type BuilderProjectQuarantineOperations = { - quarantineSnapshot: (hash: string, userId: string) => Promise - isSnapshotQuarantined: (hash: string) => Promise - purgeCacheTags: (tags: Array) => Promise - quarantineProjects: (input: { - snapshotHash: string - actorId: string - }) => Promise> - quarantineStableProjects: ( - snapshotHash: string, - actorId: string, - ) => Promise -} - -export class BuilderProjectQuarantineCleanupError extends Error { - constructor(readonly failures: Array) { - super('Builder project quarantine cleanup did not finish') - this.name = 'BuilderProjectQuarantineCleanupError' - } -} - -export async function quarantineBuilderProjectSnapshotForAdmin( - { - hash, - actorId, - }: { - hash: string - actorId: string - }, - operations: BuilderProjectQuarantineOperations = { - quarantineSnapshot: quarantineBuilderProjectSnapshot, - isSnapshotQuarantined: isBuilderProjectSnapshotQuarantined, - purgeCacheTags: purgeHostingCacheTags, - quarantineProjects: quarantineBuilderProjectsBySnapshotHash, - quarantineStableProjects: quarantineStoredBuilderProjectsBySnapshotHash, - }, -) { - const failures: Array = [] - let tombstoned = false - try { - await operations.quarantineSnapshot(hash, actorId) - tombstoned = true - } catch (error) { - failures.push(error) - } - - const purge = await operations.purgeCacheTags([ - getBuilderProjectSnapshotCacheTag(hash), - ]) - if (!purge.purged) failures.push(new Error('Cache purge failed')) - if (!tombstoned) { - try { - tombstoned = await operations.isSnapshotQuarantined(hash) - } catch (statusError) { - failures.push(statusError) - } - } - if (!tombstoned) { - throw new BuilderProjectQuarantineCleanupError(failures) - } - - let projectsQuarantined: Array = [] - try { - projectsQuarantined = await operations.quarantineProjects({ - snapshotHash: hash, - actorId, - }) - } catch (error) { - failures.push(error) - } - - let stableRecordsQuarantined = 0 - try { - stableRecordsQuarantined = - await operations.quarantineStableProjects(hash, actorId) - } catch (error) { - failures.push(error) - } - - if (failures.length > 0) { - throw new BuilderProjectQuarantineCleanupError(failures) - } - return { projectsQuarantined, purge, stableRecordsQuarantined } -} +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute( '/api/chat/project-snapshots/$hash/quarantine', )({ server: { handlers: { - POST: async ({ - params, - request, - }: { - params: { hash: string } - request: Request - }) => { - const requestError = validateSameOriginRequest(request) - if (requestError) { - return jsonError(requestError.message, requestError.status) - } - if (!isBuilderProjectSnapshotHash(params.hash)) { - return jsonError('Builder project not found', 404) - } - - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) { - return jsonError('Not authenticated', 401) - } - if (!user.capabilities.includes('admin')) { - return jsonError('Not authorized', 403) - } - - try { - const { projectsQuarantined, purge, stableRecordsQuarantined } = - await quarantineBuilderProjectSnapshotForAdmin({ - hash: params.hash, - actorId: user.userId, - }) - return jsonResponse({ - purge, - quarantined: true, - projectsQuarantined: projectsQuarantined.length, - stableRecordsQuarantined, - }) - } catch (error) { - if (error instanceof BuilderProjectQuarantineCleanupError) { - for (const failure of error.failures) { - console.error( - 'Builder project quarantine cleanup failed:', - failure, - ) - } - return jsonError('Builder quarantine cleanup is unavailable', 503) - } - if ( - error instanceof BuilderProjectSnapshotStorageUnavailableError || - error instanceof BuilderProjectStorageUnavailableError - ) { - return jsonError('Builder storage is unavailable', 503) - } - console.error('Failed to quarantine builder project:', error) - return jsonError('Failed to quarantine builder project', 500) - } - }, + POST: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/project-snapshots.$hash.ts b/src/routes/api/chat/project-snapshots.$hash.ts index ddbf4e793..7290b5eca 100644 --- a/src/routes/api/chat/project-snapshots.$hash.ts +++ b/src/routes/api/chat/project-snapshots.$hash.ts @@ -1,45 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { jsonError } from '~/utils/api-boundary.server' -import { - getBuilderProjectSnapshotCacheTag, - getBuilderProjectSnapshotObject, - isBuilderProjectSnapshotHash, - BuilderProjectSnapshotStorageUnavailableError, -} from '~/utils/builder-project-snapshot-storage.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/project-snapshots/$hash')({ server: { handlers: { - GET: async ({ params }: { params: { hash: string } }) => { - if (!isBuilderProjectSnapshotHash(params.hash)) { - return jsonError('Project snapshot not found', 404) - } - - try { - const object = await getBuilderProjectSnapshotObject(params.hash) - if (!object) return jsonError('Project snapshot not found', 404) - - const body = new Blob([await object.arrayBuffer()]) - .stream() - .pipeThrough(new DecompressionStream('gzip')) - - return new Response(body, { - headers: { - 'Cache-Control': 'public, max-age=300, must-revalidate', - 'Cache-Tag': getBuilderProjectSnapshotCacheTag(params.hash), - 'Cloudflare-CDN-Cache-Control': - 'public, max-age=31536000, immutable', - 'Content-Type': 'application/json; charset=utf-8', - ETag: `"${params.hash}"`, - }, - }) - } catch (error) { - if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { - return jsonError('Project snapshot storage is unavailable', 503) - } - throw error - } - }, + GET: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/project-snapshots.ts b/src/routes/api/chat/project-snapshots.ts index 43d44a337..831f4a5f2 100644 --- a/src/routes/api/chat/project-snapshots.ts +++ b/src/routes/api/chat/project-snapshots.ts @@ -1,118 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { - jsonError, - jsonResponse, - readJsonBody, - validateJsonRequest, -} from '~/utils/api-boundary.server' -import { - BuilderProjectSnapshotQuarantinedError, - BuilderProjectSnapshotStorageUnavailableError, - parseStoredBuilderProjectSnapshot, -} from '~/utils/builder-project-snapshot-storage.server' -import { - BuilderProjectSnapshotLimitError, - BuilderProjectSnapshotRegistryConflictError, - storeBuilderProjectSnapshotForOwner, -} from '~/utils/builder-project-snapshot-registry.server' -import { - checkIpRateLimit, - RATE_LIMITS, - rateLimitedResponse, -} from '~/utils/rateLimit.server' - -const maxRequestBytes = 2 * 1024 * 1024 +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/project-snapshots')({ server: { handlers: { - POST: async ({ request }: { request: Request }) => { - const requestError = validateJsonRequest(request, { - maxContentLength: maxRequestBytes, - }) - if (requestError) { - return jsonError(requestError.message, requestError.status) - } - - const rateLimit = await checkIpRateLimit( - request, - RATE_LIMITS.builderProjectWrite, - ) - if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) - - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) { - return jsonError('Not authenticated', 401, rateLimit.headers) - } - - const body = await readJsonBody(request, { - maxContentLength: maxRequestBytes, - }) - if (!body.success) { - return jsonError( - body.error.message, - body.error.status, - rateLimit.headers, - ) - } - - let project - try { - project = parseStoredBuilderProjectSnapshot(body.body) - } catch (error) { - return jsonError( - error instanceof Error ? error.message : 'Invalid project snapshot', - 400, - rateLimit.headers, - ) - } - - try { - const result = await storeBuilderProjectSnapshotForOwner( - user.userId, - project, - ) - return jsonResponse( - { - hash: result.hash, - url: `/builder/p/${result.hash}`, - sourceUrl: `/api/chat/project-snapshots/${result.hash}`, - }, - { - status: result.created ? 201 : 200, - headers: rateLimit.headers, - }, - ) - } catch (error) { - if (error instanceof BuilderProjectSnapshotQuarantinedError) { - return jsonError( - 'Project snapshot is unavailable', - 410, - rateLimit.headers, - ) - } - if ( - error instanceof BuilderProjectSnapshotLimitError || - error instanceof BuilderProjectSnapshotRegistryConflictError - ) { - return jsonError(error.message, 409, rateLimit.headers) - } - if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { - return jsonError( - 'Project snapshot storage is unavailable', - 503, - rateLimit.headers, - ) - } - console.error('Failed to store Builder project snapshot:', error) - return jsonError( - 'Failed to store project snapshot', - 500, - rateLimit.headers, - ) - } - }, + POST: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/projects.$id.sync.ts b/src/routes/api/chat/projects.$id.sync.ts index f348a9d8b..8386345ab 100644 --- a/src/routes/api/chat/projects.$id.sync.ts +++ b/src/routes/api/chat/projects.$id.sync.ts @@ -1,558 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { - jsonError, - jsonResponse, - readJsonBody, - validateJsonRequest, - validateSameOriginRequest, -} from '~/utils/api-boundary.server' -import { - BuilderProjectConflictError, - BuilderProjectDeletedError, - BuilderProjectLeaseError, - BuilderProjectNotFoundError, - BuilderProjectOwnershipError, - BuilderProjectRevisionConflictError, - cancelPendingBuilderProjectRun, - claimBuilderProjectRun, - createBuilderProjectThread, - enqueueBuilderProjectRun, - finishBuilderProjectRun, - getBuilderProjectMutationRequestHash, - getBuilderProjectConversationSnapshotPage, - getBuilderProjectState, - heartbeatBuilderProjectRun, - importBuilderProjectTranscript, - interruptExpiredBuilderProjectRuns, - listBuilderProjectEvents, - updateBuilderProjectState, -} from '~/utils/builder-project-events.server' -import { isBuilderProjectId } from '~/utils/builder-project' -import { getOrImportBuilderProjectState } from '~/utils/builder-project-state.server' -import { - createBuilderProjectEventStreamResponse, - isBuilderProjectSyncStreamRequest, - parseBuilderProjectSyncCursor, -} from '~/utils/builder-project-sync-http.server' -import { - encodeBuilderProjectSyncSnapshotContinuation, - parseBuilderProjectSyncSnapshotContinuation, - parseBuilderProjectSyncSnapshotPage, - parseBuilderProjectSyncRequest, - type BuilderProjectSyncCommand, - type BuilderProjectSyncCommandOutcome, -} from '~/utils/builder-project-sync' -import { - BuilderProjectSnapshotQuarantinedError, - BuilderProjectSnapshotStorageUnavailableError, -} from '~/utils/builder-project-snapshot-storage.server' -import { - BuilderProjectSnapshotLimitError, - BuilderProjectSnapshotRegistryConflictError, - storeBuilderProjectSnapshotForOwner, -} from '~/utils/builder-project-snapshot-registry.server' -import { BuilderProjectStorageUnavailableError } from '~/utils/builder-project-storage.server' -import { - checkIpRateLimit, - checkUserRateLimit, - RATE_LIMITS, - rateLimitedResponse, -} from '~/utils/rateLimit.server' - -const maxRequestBytes = 2 * 1024 * 1024 +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/projects/$id/sync')({ server: { handlers: { - GET: async ({ - params, - request, - }: { - params: { id: string } - request: Request - }) => { - const requestError = validateSameOriginRequest(request) - if (requestError) { - return jsonError(requestError.message, requestError.status) - } - if (!isBuilderProjectId(params.id)) { - return jsonError('Project not found', 404) - } - - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Not authenticated', 401) - - const streamRequest = isBuilderProjectSyncStreamRequest(request) - const continuationValues = streamRequest - ? [] - : new URL(request.url).searchParams.getAll('continuation') - let continuation - try { - if (continuationValues.length > 1) throw new Error() - const value = continuationValues[0] - continuation = value - ? parseBuilderProjectSyncSnapshotContinuation(value) - : undefined - if (continuation && continuation.projectId !== params.id) { - throw new Error() - } - } catch { - return jsonError('Invalid Builder project sync continuation', 400) - } - const rateLimitConfig = continuation - ? RATE_LIMITS.builderProjectSyncSnapshotPage - : RATE_LIMITS.builderProjectSyncStream - const ipRateLimitConfig = continuation - ? RATE_LIMITS.builderProjectSyncSnapshotPageIp - : RATE_LIMITS.builderProjectSyncStreamIp - const rateLimit = await checkUserRateLimit( - `${user.userId}:${params.id}`, - rateLimitConfig, - ) - if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) - const ipBackstop = await checkIpRateLimit(request, ipRateLimitConfig) - if (!ipBackstop.allowed) return rateLimitedResponse(ipBackstop) - - try { - if (streamRequest) { - let cursor - try { - cursor = parseBuilderProjectSyncCursor(request) - } catch { - return jsonError( - 'Invalid Builder project sync cursor', - 400, - rateLimit.headers, - ) - } - let project - try { - project = await getBuilderProjectState({ - projectId: params.id, - ownerId: user.userId, - includeDeleted: true, - }) - } catch (error) { - if (!(error instanceof BuilderProjectNotFoundError)) throw error - await getOrImportBuilderProjectState(params.id) - project = await getBuilderProjectState({ - projectId: params.id, - ownerId: user.userId, - includeDeleted: true, - }) - } - if (cursor > project.lastEventSequence) { - return jsonError( - 'Builder project sync cursor is ahead of the project', - 400, - rateLimit.headers, - ) - } - - return createBuilderProjectEventStreamResponse({ - cursor, - signal: request.signal, - headers: rateLimit.headers, - listEvents: (afterSequence) => - listBuilderProjectEvents({ - projectId: params.id, - ownerId: user.userId, - afterSequence, - limit: 25, - }), - interruptExpiredRuns: async () => { - await interruptExpiredBuilderProjectRuns({ - projectId: params.id, - ownerId: user.userId, - includeDeleted: true, - }) - }, - }) - } - - if (!continuation) await getOrImportBuilderProjectState(params.id) - const snapshot = await getBuilderProjectConversationSnapshotPage({ - projectId: params.id, - ownerId: user.userId, - ...(continuation ? { continuation } : {}), - }) - const page = parseBuilderProjectSyncSnapshotPage({ - project: snapshot.project, - cursor: snapshot.cursor, - headCursor: snapshot.headCursor, - threads: snapshot.threads, - messages: snapshot.messages, - runs: snapshot.runs, - continuation: snapshot.continuation - ? encodeBuilderProjectSyncSnapshotContinuation( - snapshot.continuation, - ) - : null, - }) - return jsonResponse(page, { headers: rateLimit.headers }) - } catch (error) { - return builderProjectSyncErrorResponse(error, rateLimit.headers) - } - }, - POST: async ({ - params, - request, - }: { - params: { id: string } - request: Request - }) => { - const requestError = validateJsonRequest(request, { - maxContentLength: maxRequestBytes, - }) - if (requestError) { - return jsonError(requestError.message, requestError.status) - } - if (!isBuilderProjectId(params.id)) { - return jsonError('Project not found', 404) - } - - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Not authenticated', 401) - - const rateLimit = await checkUserRateLimit( - `${user.userId}:${params.id}`, - RATE_LIMITS.builderProjectSyncCommand, - ) - if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) - const ipBackstop = await checkIpRateLimit( - request, - RATE_LIMITS.builderProjectSyncCommandIp, - ) - if (!ipBackstop.allowed) return rateLimitedResponse(ipBackstop) - - const body = await readJsonBody(request, { - maxContentLength: maxRequestBytes, - }) - if (!body.success) { - return jsonError( - body.error.message, - body.error.status, - rateLimit.headers, - ) - } - - let input - try { - input = parseBuilderProjectSyncRequest(body.body) - } catch { - return jsonError( - 'Invalid Builder project sync request', - 400, - rateLimit.headers, - ) - } - - try { - await getOrImportBuilderProjectState(params.id) - await getBuilderProjectState({ - projectId: params.id, - ownerId: user.userId, - }) - const results: Array = [] - for (const command of input.commands) { - try { - results.push( - await applyBuilderProjectSyncCommand({ - projectId: params.id, - ownerId: user.userId, - command, - }), - ) - } catch (error) { - if (error instanceof BuilderProjectLeaseError) { - results.push({ - clientMutationId: command.clientMutationId, - rejected: true, - code: 'run-lease-invalid', - message: error.message, - }) - continue - } - if ( - command.type === 'project.revise' && - error instanceof BuilderProjectConflictError - ) { - results.push({ - clientMutationId: command.clientMutationId, - rejected: true, - code: 'project-revision-conflict', - message: error.message, - }) - continue - } - if ( - command.type === 'run.finish' && - command.revision !== undefined && - error instanceof BuilderProjectRevisionConflictError - ) { - results.push({ - clientMutationId: command.clientMutationId, - rejected: true, - code: 'project-revision-conflict', - message: error.message, - ...(error.sequence === undefined - ? {} - : { sequence: error.sequence }), - }) - continue - } - throw error - } - } - return jsonResponse( - { - cursor: Math.max( - 0, - ...results.map((result) => result.sequence ?? 0), - ), - results, - }, - { headers: rateLimit.headers }, - ) - } catch (error) { - return builderProjectSyncErrorResponse(error, rateLimit.headers) - } - }, + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) - -async function applyBuilderProjectSyncCommand({ - projectId, - ownerId, - command, -}: { - projectId: string - ownerId: string - command: BuilderProjectSyncCommand -}) { - const requestHash = await getBuilderProjectMutationRequestHash(command) - switch (command.type) { - case 'project.revise': { - const snapshot = await storeBuilderProjectSnapshotForOwner( - ownerId, - command.project, - ) - const project = await updateBuilderProjectState({ - projectId, - ownerId, - clientMutationId: command.clientMutationId, - requestHash, - revisionId: command.revisionId, - snapshotHash: snapshot.hash, - title: command.project.title, - description: command.project.description, - expectedRevisionNumber: command.expectedRevisionNumber, - }) - return { - clientMutationId: command.clientMutationId, - sequence: project.lastEventSequence, - events: [], - } - } - case 'thread.create': - return createBuilderProjectThread({ - projectId, - ownerId, - id: command.thread.id, - clientMutationId: command.clientMutationId, - requestHash, - title: command.thread.title, - ...(command.thread.createdAt - ? { createdAt: new Date(command.thread.createdAt) } - : {}), - }) - case 'run.enqueue': - return enqueueBuilderProjectRun({ - projectId, - ownerId, - runId: command.run.id, - threadId: command.run.threadId, - clientMutationId: command.clientMutationId, - requestHash, - queueKind: command.run.queueKind, - provider: command.run.provider, - model: command.run.model, - userMessage: { - id: command.userMessage.id, - clientMutationId: command.userMessage.clientMutationId, - content: command.userMessage.content, - parts: command.userMessage.parts, - }, - }) - case 'run.claim': - return claimBuilderProjectRun({ - projectId, - ownerId, - runId: command.runId, - clientMutationId: command.clientMutationId, - requestHash, - leaseOwnerId: command.leaseOwnerId, - }) - case 'run.cancel': - return cancelPendingBuilderProjectRun({ - projectId, - ownerId, - runId: command.runId, - clientMutationId: command.clientMutationId, - requestHash, - browserSessionId: command.browserSessionId, - }) - case 'run.heartbeat': - return heartbeatBuilderProjectRun({ - projectId, - ownerId, - runId: command.runId, - clientMutationId: command.clientMutationId, - leaseOwnerId: command.leaseOwnerId, - fencingToken: command.leaseFencingToken, - }) - case 'run.finish': - const finishResult = await finishBuilderProjectRun({ - projectId, - ownerId, - runId: command.runId, - clientMutationId: command.clientMutationId, - requestHash, - status: command.status, - leaseOwnerId: command.leaseOwnerId, - fencingToken: command.leaseFencingToken, - ...(command.revision ? { revision: command.revision } : {}), - ...(command.error ? { error: command.error } : {}), - ...(command.activity ? { activity: command.activity } : {}), - ...(command.revision && command.assistantMessage - ? { - revisionConflictFallback: { - clientMutationId: - command.assistantMessage.clientMutationId, - error: { - message: - 'The project changed before the assistant edit could be saved.', - }, - }, - } - : {}), - ...(command.assistantMessage - ? { - assistantMessage: { - id: command.assistantMessage.id, - clientMutationId: - command.assistantMessage.clientMutationId, - content: command.assistantMessage.content, - parts: command.assistantMessage.parts, - ...(command.assistantMessage.createdAt - ? { - createdAt: new Date( - command.assistantMessage.createdAt, - ), - } - : {}), - }, - } - : {}), - }) - if (finishResult.revisionConflict) { - throw new BuilderProjectRevisionConflictError( - undefined, - finishResult.sequence, - ) - } - return { - clientMutationId: finishResult.clientMutationId, - sequence: finishResult.sequence, - events: finishResult.events, - ...(finishResult.leaseFencingToken === undefined - ? {} - : { leaseFencingToken: finishResult.leaseFencingToken }), - } - case 'transcript.import': - return importBuilderProjectTranscript({ - projectId, - ownerId, - clientMutationId: command.clientMutationId, - requestHash, - threads: command.threads.map((thread) => ({ - id: thread.id, - title: thread.title, - createdAt: new Date(thread.createdAt), - updatedAt: new Date(thread.updatedAt), - ...(thread.archivedAt - ? { archivedAt: new Date(thread.archivedAt) } - : {}), - })), - messages: command.messages.map((message) => ({ - id: message.id, - threadId: message.threadId, - ...(message.runId ? { runId: message.runId } : {}), - role: message.role, - content: message.content, - parts: message.parts, - position: message.position, - createdAt: new Date(message.createdAt), - updatedAt: new Date(message.updatedAt), - })), - runs: command.runs.map((run) => ({ - id: run.id, - threadId: run.threadId, - status: run.status, - provider: run.provider, - model: run.model, - ...(run.error ? { error: run.error } : {}), - ...(run.activity ? { activity: run.activity } : {}), - ...(run.startedAt ? { startedAt: new Date(run.startedAt) } : {}), - completedAt: new Date(run.completedAt), - })), - }) - } -} - -function builderProjectSyncErrorResponse( - error: unknown, - headers: HeadersInit, -) { - if (error instanceof BuilderProjectNotFoundError) { - return jsonError('Project not found', 404, headers) - } - if (error instanceof BuilderProjectOwnershipError) { - return jsonError('Not authorized', 403, headers) - } - if (error instanceof BuilderProjectSnapshotRegistryConflictError) { - return jsonError( - 'Project snapshot must be uploaded before completing the run', - 409, - headers, - ) - } - if (error instanceof BuilderProjectSnapshotQuarantinedError) { - return jsonError('Builder project is unavailable', 410, headers) - } - if (error instanceof BuilderProjectSnapshotLimitError) { - return jsonError(error.message, 409, headers) - } - if (error instanceof BuilderProjectSnapshotStorageUnavailableError) { - return jsonError( - 'Builder project snapshot storage is unavailable', - 503, - headers, - ) - } - if (error instanceof BuilderProjectStorageUnavailableError) { - return jsonError('Builder project storage is unavailable', 503, headers) - } - if ( - error instanceof BuilderProjectConflictError || - error instanceof BuilderProjectDeletedError || - error instanceof BuilderProjectLeaseError - ) { - return jsonError(error.message, 409, headers) - } - console.error('Builder project sync failed:', error) - return jsonError('Failed to sync Builder project', 500, headers) -} diff --git a/src/routes/api/chat/projects.$id.ts b/src/routes/api/chat/projects.$id.ts index 35d03fd69..f2b56d95f 100644 --- a/src/routes/api/chat/projects.$id.ts +++ b/src/routes/api/chat/projects.$id.ts @@ -1,386 +1,12 @@ import { createFileRoute } from '@tanstack/react-router' -import { - jsonError, - jsonResponse, - readJsonBody, - validateJsonRequest, -} from '~/utils/api-boundary.server' -import { isBuilderProjectId } from '~/utils/builder-project' -import { - BuilderProjectConflictError, - BuilderProjectDeletedError, - BuilderProjectNotFoundError, - BuilderProjectOwnershipError, - deleteBuilderProjectState, - getBuilderProjectMutationRequestHash, - updateBuilderProjectState, -} from '~/utils/builder-project-events.server' -import { - getOrImportBuilderProjectState, - toBuilderProject, -} from '~/utils/builder-project-state.server' -import { BuilderProjectStorageUnavailableError } from '~/utils/builder-project-storage.server' -import { - BuilderProjectSnapshotQuarantinedError, - BuilderProjectSnapshotStorageUnavailableError, - parseStoredBuilderProjectSnapshot, -} from '~/utils/builder-project-snapshot-storage.server' -import { - BuilderProjectSnapshotLimitError, - BuilderProjectSnapshotRegistryConflictError, - storeBuilderProjectSnapshotForOwner, -} from '~/utils/builder-project-snapshot-registry.server' -import { - checkIpRateLimit, - RATE_LIMITS, - rateLimitedResponse, -} from '~/utils/rateLimit.server' - -const maxRequestBytes = 2 * 1024 * 1024 +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/projects/$id')({ server: { handlers: { - GET: async ({ params }: { params: { id: string } }) => { - if (!isBuilderProjectId(params.id)) { - return jsonError('Project not found', 404) - } - - try { - const project = await getOrImportBuilderProjectState(params.id) - return jsonResponse({ project: toBuilderProject(project) }) - } catch (error) { - if (error instanceof BuilderProjectNotFoundError) { - return jsonError('Project not found', 404) - } - if (error instanceof BuilderProjectSnapshotQuarantinedError) { - return jsonError('Builder project is unavailable', 410) - } - if ( - error instanceof BuilderProjectSnapshotLimitError || - error instanceof BuilderProjectSnapshotRegistryConflictError - ) { - return jsonError(error.message, 409) - } - if ( - error instanceof BuilderProjectSnapshotStorageUnavailableError - ) { - return jsonError('Builder project storage is unavailable', 503) - } - if (error instanceof BuilderProjectStorageUnavailableError) { - return jsonError('Builder project storage is unavailable', 503) - } - console.error('Failed to load builder project:', error) - return jsonError('Failed to load builder project', 500) - } - }, - PATCH: async ({ - params, - request, - }: { - params: { id: string } - request: Request - }) => { - const requestError = validateJsonRequest(request, { - maxContentLength: maxRequestBytes, - }) - if (requestError) { - return jsonError(requestError.message, requestError.status) - } - if (!isBuilderProjectId(params.id)) { - return jsonError('Project not found', 404) - } - - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Not authenticated', 401) - - const rateLimit = await checkIpRateLimit( - request, - RATE_LIMITS.builderProjectSave, - ) - if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) - - let existing - try { - existing = await getOrImportBuilderProjectState(params.id) - } catch (error) { - if (error instanceof BuilderProjectNotFoundError) { - return jsonError('Project not found', 404, rateLimit.headers) - } - if (error instanceof BuilderProjectSnapshotQuarantinedError) { - return jsonError( - 'Builder project is unavailable', - 410, - rateLimit.headers, - ) - } - if ( - error instanceof BuilderProjectSnapshotLimitError || - error instanceof BuilderProjectSnapshotRegistryConflictError - ) { - return jsonError(error.message, 409, rateLimit.headers) - } - if ( - error instanceof BuilderProjectSnapshotStorageUnavailableError - ) { - return jsonError( - 'Builder project storage is unavailable', - 503, - rateLimit.headers, - ) - } - if (error instanceof BuilderProjectStorageUnavailableError) { - return jsonError( - 'Builder project storage is unavailable', - 503, - rateLimit.headers, - ) - } - console.error('Failed to load builder project for update:', error) - return jsonError( - 'Failed to load builder project', - 500, - rateLimit.headers, - ) - } - if (existing.ownerId !== user.userId) { - return jsonError('Not authorized', 403, rateLimit.headers) - } - - const body = await readJsonBody(request, { - maxContentLength: maxRequestBytes, - }) - if (!body.success) { - return jsonError( - body.error.message, - body.error.status, - rateLimit.headers, - ) - } - - let input - try { - input = parseBuilderProjectRequest(body.body) - } catch (error) { - return jsonError( - error instanceof Error ? error.message : 'Invalid builder project', - 400, - rateLimit.headers, - ) - } - - try { - const snapshot = input.project - const requestHash = await getBuilderProjectMutationRequestHash({ - type: 'project.revise', - ...input, - }) - const storedSnapshot = await storeBuilderProjectSnapshotForOwner( - user.userId, - snapshot, - ) - const project = await updateBuilderProjectState({ - clientMutationId: input.clientMutationId, - requestHash, - description: snapshot.description, - expectedRevisionNumber: input.expectedRevisionNumber, - projectId: existing.id, - revisionId: input.revisionId, - ownerId: user.userId, - snapshotHash: storedSnapshot.hash, - title: snapshot.title, - }) - return jsonResponse( - { project: toBuilderProject(project) }, - { headers: rateLimit.headers }, - ) - } catch (error) { - if ( - error instanceof BuilderProjectSnapshotLimitError || - error instanceof BuilderProjectSnapshotRegistryConflictError - ) { - return jsonError(error.message, 409, rateLimit.headers) - } - if (error instanceof BuilderProjectConflictError) { - return jsonError(error.message, 409, rateLimit.headers) - } - if (error instanceof BuilderProjectOwnershipError) { - return jsonError('Not authorized', 403, rateLimit.headers) - } - if ( - error instanceof BuilderProjectDeletedError || - error instanceof BuilderProjectSnapshotQuarantinedError - ) { - return jsonError( - 'Builder project is unavailable', - 410, - rateLimit.headers, - ) - } - if ( - error instanceof BuilderProjectStorageUnavailableError || - error instanceof BuilderProjectSnapshotStorageUnavailableError - ) { - return jsonError( - 'Builder storage is unavailable', - 503, - rateLimit.headers, - ) - } - console.error('Failed to update builder project:', error) - return jsonError( - 'Failed to update builder project', - 500, - rateLimit.headers, - ) - } - }, - DELETE: async ({ - params, - request, - }: { - params: { id: string } - request: Request - }) => { - const requestError = validateJsonRequest(request, { - maxContentLength: 1_024, - }) - if (requestError) { - return jsonError(requestError.message, requestError.status) - } - if (!isBuilderProjectId(params.id)) { - return jsonError('Project not found', 404) - } - - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Not authenticated', 401) - - const rateLimit = await checkIpRateLimit( - request, - RATE_LIMITS.builderProjectWrite, - ) - if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) - - const body = await readJsonBody(request, { maxContentLength: 1_024 }) - if (!body.success) { - return jsonError( - body.error.message, - body.error.status, - rateLimit.headers, - ) - } - let clientMutationId - try { - clientMutationId = parseDeleteBuilderProjectRequest(body.body) - } catch { - return jsonError( - 'Invalid Builder project delete request', - 400, - rateLimit.headers, - ) - } - - try { - await getOrImportBuilderProjectState(params.id) - await deleteBuilderProjectState({ - projectId: params.id, - ownerId: user.userId, - clientMutationId, - }) - return jsonResponse( - { deleted: true, id: params.id }, - { headers: rateLimit.headers }, - ) - } catch (error) { - if (error instanceof BuilderProjectNotFoundError) { - return jsonError('Project not found', 404, rateLimit.headers) - } - if (error instanceof BuilderProjectSnapshotQuarantinedError) { - return jsonError( - 'Builder project is unavailable', - 410, - rateLimit.headers, - ) - } - if ( - error instanceof BuilderProjectSnapshotLimitError || - error instanceof BuilderProjectSnapshotRegistryConflictError - ) { - return jsonError(error.message, 409, rateLimit.headers) - } - if (error instanceof BuilderProjectOwnershipError) { - return jsonError('Not authorized', 403, rateLimit.headers) - } - if ( - error instanceof BuilderProjectStorageUnavailableError || - error instanceof BuilderProjectSnapshotStorageUnavailableError - ) { - return jsonError( - 'Builder project storage is unavailable', - 503, - rateLimit.headers, - ) - } - console.error('Failed to delete builder project:', error) - return jsonError( - 'Failed to delete builder project', - 500, - rateLimit.headers, - ) - } - }, + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + PATCH: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) - -function parseBuilderProjectRequest(value: unknown) { - if ( - typeof value !== 'object' || - value === null || - Array.isArray(value) || - Object.keys(value).some( - (key) => - key !== 'clientMutationId' && - key !== 'expectedRevisionNumber' && - key !== 'project' && - key !== 'revisionId', - ) || - !('clientMutationId' in value) || - !('expectedRevisionNumber' in value) || - !('project' in value) || - !('revisionId' in value) || - typeof value.clientMutationId !== 'string' || - !isBuilderProjectId(value.clientMutationId) || - typeof value.expectedRevisionNumber !== 'number' || - !Number.isSafeInteger(value.expectedRevisionNumber) || - value.expectedRevisionNumber < 1 || - typeof value.revisionId !== 'string' || - !isBuilderProjectId(value.revisionId) - ) { - throw new Error('Invalid builder project request') - } - - return { - clientMutationId: value.clientMutationId, - expectedRevisionNumber: value.expectedRevisionNumber, - project: parseStoredBuilderProjectSnapshot(value.project), - revisionId: value.revisionId, - } -} - -function parseDeleteBuilderProjectRequest(value: unknown) { - if ( - typeof value !== 'object' || - value === null || - Array.isArray(value) || - Object.keys(value).some((key) => key !== 'clientMutationId') || - !('clientMutationId' in value) || - typeof value.clientMutationId !== 'string' || - !isBuilderProjectId(value.clientMutationId) - ) { - throw new Error('Invalid Builder project delete request') - } - return value.clientMutationId -} diff --git a/src/routes/api/chat/projects.ts b/src/routes/api/chat/projects.ts index 5c409be82..866b49235 100644 --- a/src/routes/api/chat/projects.ts +++ b/src/routes/api/chat/projects.ts @@ -1,296 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { - jsonError, - jsonResponse, - readJsonBody, - validateJsonRequest, -} from '~/utils/api-boundary.server' -import { - BuilderProjectLimitError, - BuilderProjectNotFoundError, - BuilderProjectConflictError, - createBuilderProjectState, - preflightBuilderProjectCreation, -} from '~/utils/builder-project-events.server' -import { - getOrImportBuilderProjectState, - listOrImportBuilderProjectStates, - reserveLegacyBuilderProjectId, - toBuilderProject, -} from '~/utils/builder-project-state.server' -import { - BuilderProjectStorageUnavailableError, -} from '~/utils/builder-project-storage.server' -import { isBuilderProjectId } from '~/utils/builder-project' -import { - BuilderProjectSnapshotQuarantinedError, - BuilderProjectSnapshotStorageUnavailableError, - getBuilderProjectSnapshotHash, - parseStoredBuilderProjectSnapshot, -} from '~/utils/builder-project-snapshot-storage.server' -import { - BuilderProjectSnapshotLimitError, - BuilderProjectSnapshotRegistryConflictError, - storeBuilderProjectSnapshotForOwner, -} from '~/utils/builder-project-snapshot-registry.server' -import { - checkIpRateLimit, - checkUserWindowRateLimit, - RATE_LIMITS, - rateLimitedResponse, -} from '~/utils/rateLimit.server' - -const maxRequestBytes = 2 * 1024 * 1024 +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/projects')({ server: { handlers: { - GET: async ({ request }: { request: Request }) => { - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Not authenticated', 401) - - const rateLimit = await checkIpRateLimit( - request, - RATE_LIMITS.builderProjectList, - ) - if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) - - try { - const projects = await listOrImportBuilderProjectStates(user.userId) - return jsonResponse( - { projects: projects.map(toBuilderProject) }, - { headers: rateLimit.headers }, - ) - } catch (error) { - if (error instanceof BuilderProjectSnapshotQuarantinedError) { - return jsonError( - 'Builder project is unavailable', - 410, - rateLimit.headers, - ) - } - if ( - error instanceof BuilderProjectSnapshotLimitError || - error instanceof BuilderProjectSnapshotRegistryConflictError - ) { - return jsonError(error.message, 409, rateLimit.headers) - } - if ( - error instanceof BuilderProjectSnapshotStorageUnavailableError - ) { - return jsonError( - 'Builder project storage is unavailable', - 503, - rateLimit.headers, - ) - } - if (error instanceof BuilderProjectStorageUnavailableError) { - return jsonError( - 'Builder project storage is unavailable', - 503, - rateLimit.headers, - ) - } - console.error('Failed to list builder projects:', error) - return jsonError( - 'Failed to list builder projects', - 500, - rateLimit.headers, - ) - } - }, - POST: async ({ request }: { request: Request }) => { - const requestError = validateJsonRequest(request, { - maxContentLength: maxRequestBytes, - }) - if (requestError) { - return jsonError(requestError.message, requestError.status) - } - - const { getAuthService } = await import('~/auth/index.server') - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Not authenticated', 401) - - const rateLimit = await checkIpRateLimit( - request, - RATE_LIMITS.builderProjectWrite, - ) - if (!rateLimit.allowed) return rateLimitedResponse(rateLimit) - - const body = await readJsonBody(request, { - maxContentLength: maxRequestBytes, - }) - if (!body.success) { - return jsonError( - body.error.message, - body.error.status, - rateLimit.headers, - ) - } - - let input - try { - input = parseBuilderProjectRequest(body.body) - } catch (error) { - return jsonError( - error instanceof Error ? error.message : 'Invalid builder project', - 400, - rateLimit.headers, - ) - } - - try { - await listOrImportBuilderProjectStates(user.userId) - const snapshotHash = await getBuilderProjectSnapshotHash( - input.project, - ) - const existing = await preflightBuilderProjectCreation({ - id: input.id, - ownerId: user.userId, - clientMutationId: input.clientMutationId, - revisionId: input.revisionId, - snapshotHash, - title: input.project.title, - description: input.project.description, - ...(input.forkedFromId - ? { forkedFromId: input.forkedFromId } - : {}), - }) - if (existing) { - return jsonResponse( - { project: toBuilderProject(existing) }, - { status: 201, headers: rateLimit.headers }, - ) - } - const creationLimit = await checkUserWindowRateLimit( - user.userId, - RATE_LIMITS.builderProjectCreateDaily, - ) - if (!creationLimit.allowed) { - return rateLimitedResponse(creationLimit) - } - await reserveLegacyBuilderProjectId(input.id) - - if (input.forkedFromId) { - try { - await getOrImportBuilderProjectState(input.forkedFromId) - } catch (error) { - if (error instanceof BuilderProjectNotFoundError) { - return jsonError( - 'Fork source builder was not found', - 400, - rateLimit.headers, - ) - } - throw error - } - } - - const snapshot = input.project - const storedSnapshot = await storeBuilderProjectSnapshotForOwner( - user.userId, - snapshot, - ) - const state = await createBuilderProjectState({ - id: input.id, - revisionId: input.revisionId, - clientMutationId: input.clientMutationId, - description: snapshot.description, - forkedFromId: input.forkedFromId, - ownerId: user.userId, - snapshotHash: storedSnapshot.hash, - title: snapshot.title, - }) - return jsonResponse( - { project: toBuilderProject(state) }, - { status: 201, headers: rateLimit.headers }, - ) - } catch (error) { - if (error instanceof BuilderProjectLimitError) { - return jsonError(error.message, 409, rateLimit.headers) - } - if ( - error instanceof BuilderProjectSnapshotLimitError || - error instanceof BuilderProjectSnapshotRegistryConflictError - ) { - return jsonError(error.message, 409, rateLimit.headers) - } - if (error instanceof BuilderProjectConflictError) { - return jsonError(error.message, 409, rateLimit.headers) - } - if ( - error instanceof BuilderProjectSnapshotQuarantinedError - ) { - return jsonError( - 'Builder project is unavailable', - 410, - rateLimit.headers, - ) - } - if ( - error instanceof BuilderProjectStorageUnavailableError || - error instanceof BuilderProjectSnapshotStorageUnavailableError - ) { - return jsonError( - 'Builder storage is unavailable', - 503, - rateLimit.headers, - ) - } - console.error('Failed to create builder project:', error) - return jsonError( - 'Failed to create builder project', - 500, - rateLimit.headers, - ) - } - }, + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) - -function parseBuilderProjectRequest(value: unknown) { - if ( - typeof value !== 'object' || - value === null || - Array.isArray(value) || - Object.keys(value).some( - (key) => - key !== 'clientMutationId' && - key !== 'forkedFromId' && - key !== 'id' && - key !== 'project' && - key !== 'revisionId', - ) || - !('clientMutationId' in value) || - !('id' in value) || - !('project' in value) || - !('revisionId' in value) || - typeof value.clientMutationId !== 'string' || - !isBuilderProjectId(value.clientMutationId) || - typeof value.id !== 'string' || - !isBuilderProjectId(value.id) || - typeof value.revisionId !== 'string' || - !isBuilderProjectId(value.revisionId) - ) { - throw new Error('Invalid builder project request') - } - - const forkedFromId = - 'forkedFromId' in value ? value.forkedFromId : undefined - if ( - forkedFromId !== undefined && - (typeof forkedFromId !== 'string' || !isBuilderProjectId(forkedFromId)) - ) { - throw new Error('Invalid fork source builder ID') - } - - return { - clientMutationId: value.clientMutationId, - id: value.id, - project: parseStoredBuilderProjectSnapshot(value.project), - revisionId: value.revisionId, - ...(forkedFromId ? { forkedFromId } : {}), - } -} diff --git a/src/routes/api/chat/recipes.ts b/src/routes/api/chat/recipes.ts index 0ecf5c2e4..5bd18baf6 100644 --- a/src/routes/api/chat/recipes.ts +++ b/src/routes/api/chat/recipes.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleSavedActions} from '~/chat/server/saved-actions-http.server' -export const Route=createFileRoute('/api/chat/recipes')({server:{handlers:{POST:({request})=>handleSavedActions(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/recipes')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/recipes/$id.ts b/src/routes/api/chat/recipes/$id.ts index 3192e5d9d..6a4425610 100644 --- a/src/routes/api/chat/recipes/$id.ts +++ b/src/routes/api/chat/recipes/$id.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleSavedActions} from '~/chat/server/saved-actions-http.server' -export const Route=createFileRoute('/api/chat/recipes/$id')({server:{handlers:{DELETE:({request,params})=>handleSavedActions(request,params.id)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/recipes/$id')({ + server: { + handlers: { + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/references.ts b/src/routes/api/chat/references.ts index 9e1448e71..0673bd879 100644 --- a/src/routes/api/chat/references.ts +++ b/src/routes/api/chat/references.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleReferences} from '~/chat/server/reference-http.server' -export const Route=createFileRoute('/api/chat/references')({server:{handlers:{GET:({request})=>handleReferences(request)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/references')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/references/kody/inspect.ts b/src/routes/api/chat/references/kody/inspect.ts index 20193572e..04e8aeaed 100644 --- a/src/routes/api/chat/references/kody/inspect.ts +++ b/src/routes/api/chat/references/kody/inspect.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleReferenceCatalog} from '~/chat/server/reference-catalog-http.server' -export const Route=createFileRoute('/api/chat/references/kody/inspect')({server:{handlers:{GET:({request})=>handleReferenceCatalog(request,'kody-inspect')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/references/kody/inspect')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/references/kody/refresh.ts b/src/routes/api/chat/references/kody/refresh.ts index bc0082c46..1c329b388 100644 --- a/src/routes/api/chat/references/kody/refresh.ts +++ b/src/routes/api/chat/references/kody/refresh.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleReferenceCatalog} from '~/chat/server/reference-catalog-http.server' -export const Route=createFileRoute('/api/chat/references/kody/refresh')({server:{handlers:{POST:({request})=>handleReferenceCatalog(request,'kody-refresh')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/references/kody/refresh')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/references/tools/refresh.ts b/src/routes/api/chat/references/tools/refresh.ts index 7d6a84be2..9d6258b07 100644 --- a/src/routes/api/chat/references/tools/refresh.ts +++ b/src/routes/api/chat/references/tools/refresh.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleReferenceCatalog} from '~/chat/server/reference-catalog-http.server' -export const Route=createFileRoute('/api/chat/references/tools/refresh')({server:{handlers:{POST:({request})=>handleReferenceCatalog(request,'tools-refresh')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/references/tools/refresh')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/retries/$id.prepare.ts b/src/routes/api/chat/retries/$id.prepare.ts index 3c0b886af..545cd5c0f 100644 --- a/src/routes/api/chat/retries/$id.prepare.ts +++ b/src/routes/api/chat/retries/$id.prepare.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleConversationRetry} from '~/chat/server/conversation-retry-http.server' -export const Route=createFileRoute('/api/chat/retries/$id/prepare')({server:{handlers:{POST:({request,params})=>handleConversationRetry(request,params.id,'prepare')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/retries/$id/prepare')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/retries/$id.ts b/src/routes/api/chat/retries/$id.ts index 4e94643bf..b110765a6 100644 --- a/src/routes/api/chat/retries/$id.ts +++ b/src/routes/api/chat/retries/$id.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleConversationRetry} from '~/chat/server/conversation-retry-http.server' -export const Route=createFileRoute('/api/chat/retries/$id')({server:{handlers:{GET:({request,params})=>handleConversationRetry(request,params.id,'get')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/retries/$id')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/sections.ts b/src/routes/api/chat/sections.ts index 9a6a4ba76..dc5cd086e 100644 --- a/src/routes/api/chat/sections.ts +++ b/src/routes/api/chat/sections.ts @@ -1,3 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleWorkspaceSections } from '~/chat/server/workspace-sections-http.server' -export const Route=createFileRoute('/api/chat/sections')({server:{handlers:{POST:({request})=>handleWorkspaceSections(request)}}}) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/sections')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/sections/$id.ts b/src/routes/api/chat/sections/$id.ts index f35a13dfc..27daa91c1 100644 --- a/src/routes/api/chat/sections/$id.ts +++ b/src/routes/api/chat/sections/$id.ts @@ -1,3 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleWorkspaceSections } from '~/chat/server/workspace-sections-http.server' -export const Route=createFileRoute('/api/chat/sections/$id')({server:{handlers:{PATCH:({request,params})=>handleWorkspaceSections(request,params.id),DELETE:({request,params})=>handleWorkspaceSections(request,params.id)}}}) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/sections/$id')({ + server: { + handlers: { + PATCH: ({ request }) => forwardWorkerRequest('CHAT', request), + DELETE: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/skills.ts b/src/routes/api/chat/skills.ts index 99e804384..4358c8cf2 100644 --- a/src/routes/api/chat/skills.ts +++ b/src/routes/api/chat/skills.ts @@ -1,28 +1,11 @@ -import { hasChatAccess } from '~/chat/access.server' -import { z } from 'zod' import { createFileRoute } from '@tanstack/react-router' -import { getAuthService } from '~/auth/index.server' -import { jsonError,validateSameOriginRequest } from '~/utils/api-boundary.server' -import { readWorkspacePolicy,WorkspacePolicyError } from '~/chat/workspace-policy.server' -import { getKodyEnvironment,KodyEnvironmentError } from '~/chat/server/kody-environment.server' -import { skillApi } from '~/chat/server/skill-api' -export async function handleSkills(request:Request, id?:string) { - if(request.method==='POST') { - const error=validateSameOriginRequest(request) - if(error) return jsonError(error.message,error.status) - } - const user=await getAuthService().getCurrentUser(request) - if(!user) return jsonError('Sign in to continue.',401) - if(!await hasChatAccess(user)) return jsonError('Chat access is unavailable.',403) - const url=new URL(request.url) - const parsed=z.string().min(1).max(1000).safeParse(url.searchParams.get('workspaceId')) - if(url.searchParams.getAll('workspaceId').length!==1||!parsed.success) return jsonError('Choose a workspace.',400) - try { - await readWorkspacePolicy(parsed.data,user.userId) - return await skillApi(request,await getKodyEnvironment(),{workspaceId:parsed.data,userId:user.userId},id??url.searchParams.get('id')??undefined) - } catch(error) { - if(error instanceof WorkspacePolicyError||error instanceof KodyEnvironmentError) return jsonError(error.message,error.status) - throw error - } -} -export const Route=createFileRoute('/api/chat/skills')({server:{handlers:{GET:({request})=>handleSkills(request),POST:({request})=>handleSkills(request)}}}) +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/skills')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/skills/$id.ts b/src/routes/api/chat/skills/$id.ts index 86f245dcb..e2d60032f 100644 --- a/src/routes/api/chat/skills/$id.ts +++ b/src/routes/api/chat/skills/$id.ts @@ -1,11 +1,11 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleSkills } from '../skills' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/skills/$id')({ server: { handlers: { - GET: ({ request, params }) => handleSkills(request, params.id), - POST: ({ request, params }) => handleSkills(request, params.id), + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + POST: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/chat/workspace-history.ts b/src/routes/api/chat/workspace-history.ts index 33fa26dd9..180f4ca43 100644 --- a/src/routes/api/chat/workspace-history.ts +++ b/src/routes/api/chat/workspace-history.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleBotMutation} from '~/chat/server/bot-mutation-http.server' -export const Route=createFileRoute('/api/chat/workspace-history')({server:{handlers:{POST:({request})=>handleBotMutation(request,undefined,undefined,'history')}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/workspace-history')({ + server: { + handlers: { + POST: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/workspace-index.activity.ts b/src/routes/api/chat/workspace-index.activity.ts index 9ed2c2ba8..741887d80 100644 --- a/src/routes/api/chat/workspace-index.activity.ts +++ b/src/routes/api/chat/workspace-index.activity.ts @@ -1,3 +1,10 @@ -import {createFileRoute} from '@tanstack/react-router' -import {handleWorkspaceActivity} from '~/chat/server/workspace-activity-http.server' -export const Route=createFileRoute('/api/chat/workspace-index/activity')({server:{handlers:{GET:({request})=>handleWorkspaceActivity(request,true)}}}) +import { createFileRoute } from '@tanstack/react-router' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + +export const Route = createFileRoute('/api/chat/workspace-index/activity')({ + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, +}) diff --git a/src/routes/api/chat/workspace-index.ts b/src/routes/api/chat/workspace-index.ts index 342e65115..51c2daacf 100644 --- a/src/routes/api/chat/workspace-index.ts +++ b/src/routes/api/chat/workspace-index.ts @@ -1,5 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleWorkspaceIndex } from '~/chat/server/workspace-index-http.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + export const Route = createFileRoute('/api/chat/workspace-index')({ - server: { handlers: { GET: ({request}) => handleWorkspaceIndex(request) } }, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/api/chat/workspace-sync/$operation.ts b/src/routes/api/chat/workspace-sync/$operation.ts index bb0f363d9..e3ddee49d 100644 --- a/src/routes/api/chat/workspace-sync/$operation.ts +++ b/src/routes/api/chat/workspace-sync/$operation.ts @@ -1,5 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleWorkspaceSync } from '~/chat/server/workspace-sync-http.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + export const Route = createFileRoute('/api/chat/workspace-sync/$operation')({ - server: {handlers: {GET: ({request,params}) => handleWorkspaceSync(request,params.operation)}}, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/api/chat/workspace.ts b/src/routes/api/chat/workspace.ts index a86f84914..fd135a489 100644 --- a/src/routes/api/chat/workspace.ts +++ b/src/routes/api/chat/workspace.ts @@ -1,22 +1,10 @@ -import { hasChatAccess } from '~/chat/access.server' import { createFileRoute } from '@tanstack/react-router' -import { getAuthService } from '~/auth/index.server' -import { openPersonalChatWorkspace } from '~/chat/workspace.server' -import { jsonError, jsonResponse, validateSameOriginRequest } from '~/utils/api-boundary.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/chat/workspace')({ server: { handlers: { - POST: async ({ request }) => { - const boundaryError = validateSameOriginRequest(request) - if (boundaryError) return jsonError(boundaryError.message, boundaryError.status) - const user = await getAuthService().getCurrentUser(request) - if (!user) return jsonError('Sign in to TanStack.', 401) - if (!await hasChatAccess(user)) - return jsonError('Chat access is not enabled for this account.', 403) - const workspace = await openPersonalChatWorkspace(user.userId) - return jsonResponse(workspace) - }, + POST: ({ request }) => forwardWorkerRequest('CHAT', request), }, }, }) diff --git a/src/routes/api/og/{$}[.]png.ts b/src/routes/api/og/{$}[.]png.ts index f399f2d0b..1a54b3e8d 100644 --- a/src/routes/api/og/{$}[.]png.ts +++ b/src/routes/api/og/{$}[.]png.ts @@ -1,64 +1,8 @@ import { createFileRoute } from '@tanstack/react-router' - -type GenerateOgImageResponse = typeof import( - '~/server/og/generate.server' -)['generateOgImageResponse'] - -const CACHE_HEADERS = { - 'Cache-Control': 'public, max-age=3600', - 'Cloudflare-CDN-Cache-Control': - 'public, max-age=86400, stale-while-revalidate=604800', -} as const +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/og/{$}.png')({ server: { - handlers: { - GET: async ({ - request, - params, - }: { - request: Request - params: { _splat: string } - }) => { - const rawParam = params._splat - const libraryId = rawParam.replace(/\.png$/, '') - - const url = new URL(request.url) - let result: Awaited> - try { - const { generateOgImageResponse } = await import( - '~/server/og/generate.server' - ) - result = await generateOgImageResponse( - { - libraryId, - requestUrl: request.url, - title: url.searchParams.get('title') ?? undefined, - description: url.searchParams.get('description') ?? undefined, - }, - { headers: CACHE_HEADERS }, - ) - } catch (error) { - console.error('Failed to construct OG response', error) - return new Response('Failed to generate OG image', { status: 500 }) - } - - if ('kind' in result) { - return new Response(`Unknown library: ${libraryId}`, { status: 404 }) - } - - // ImageResponse builds the Response synchronously and renders inside - // a ReadableStream. Await the ready promise so render errors surface - // as 500s instead of an empty 200 cached at the edge. - try { - await result.ready - } catch (error) { - console.error('Failed to generate OG image', error) - return new Response('Failed to generate OG image', { status: 500 }) - } - - return result - }, - }, + handlers: { GET: ({ request }) => forwardWorkerRequest('OG', request) }, }, }) diff --git a/src/routes/api/readme/{$}[.]png.ts b/src/routes/api/readme/{$}[.]png.ts index b6298392d..e70ca7210 100644 --- a/src/routes/api/readme/{$}[.]png.ts +++ b/src/routes/api/readme/{$}[.]png.ts @@ -1,101 +1,8 @@ import { createFileRoute } from '@tanstack/react-router' -import { findLibrary } from '~/libraries' -import type { Framework } from '~/libraries/types' -import { OG_THEMES, isOgTheme } from '~/server/og/colors' - -type GenerateReadmeHeaderResponse = typeof import( - '~/server/og/generate.server' -)['generateReadmeHeaderResponse'] - -const CACHE_HEADERS = { - 'Cache-Control': 'public, max-age=3600', - 'Cloudflare-CDN-Cache-Control': - 'public, max-age=86400, stale-while-revalidate=604800', -} as const +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' export const Route = createFileRoute('/api/readme/{$}.png')({ server: { - handlers: { - GET: async ({ - request, - params, - }: { - request: Request - params: { _splat: string } - }) => { - const libraryId = params._splat.replace(/\.png$/, '') - const library = findLibrary(libraryId) - if (!library) { - return new Response(`Unknown library: ${libraryId}`, { status: 404 }) - } - - const url = new URL(request.url) - - // Validated rather than ignored: a typo'd framework in a README should - // surface as a broken image, not as a banner naming the wrong package. - // An omitted param means "no framework"; a present-but-blank one - // (`?framework=`) is a malformed URL, not a request for the default. - const framework = url.searchParams.get('framework') ?? undefined - if ( - framework !== undefined && - !library.frameworks.includes(framework as Framework) - ) { - return new Response( - `Unknown framework "${framework}" for ${library.name}. Expected one of: ${library.frameworks.join(', ')}`, - { status: 400 }, - ) - } - - // Same contract as `framework`: supplied means it must be valid. - const themeParam = url.searchParams.get('theme') ?? undefined - if (themeParam !== undefined && !isOgTheme(themeParam)) { - return new Response( - `Unknown theme "${themeParam}". Expected one of: ${OG_THEMES.join(', ')}`, - { status: 400 }, - ) - } - - let result: Awaited> - try { - const { generateReadmeHeaderResponse } = await import( - '~/server/og/generate.server' - ) - result = await generateReadmeHeaderResponse( - { - libraryId, - requestUrl: request.url, - framework: framework as Framework | undefined, - title: url.searchParams.get('title') ?? undefined, - subtitle: url.searchParams.get('subtitle') ?? undefined, - theme: themeParam, - }, - { headers: CACHE_HEADERS }, - ) - } catch (error) { - console.error('Failed to construct README header response', error) - return new Response('Failed to generate README header', { - status: 500, - }) - } - - if ('kind' in result) { - return new Response(`Unknown library: ${libraryId}`, { status: 404 }) - } - - // ImageResponse builds the Response synchronously and renders inside - // a ReadableStream. Await the ready promise so render errors surface - // as 500s instead of an empty 200 cached at the edge. - try { - await result.ready - } catch (error) { - console.error('Failed to generate README header', error) - return new Response('Failed to generate README header', { - status: 500, - }) - } - - return result - }, - }, + handlers: { GET: ({ request }) => forwardWorkerRequest('OG', request) }, }, }) diff --git a/src/routes/auth/mcp/callback.ts b/src/routes/auth/mcp/callback.ts index b6d9c4eac..ec203b520 100644 --- a/src/routes/auth/mcp/callback.ts +++ b/src/routes/auth/mcp/callback.ts @@ -1,5 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleMcpOAuth } from '~/chat/server/mcp-oauth-route.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + export const Route = createFileRoute('/auth/mcp/callback')({ - server: { handlers: { GET: ({ request }) => handleMcpOAuth(request) } }, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/auth/mcp/client-metadata.ts b/src/routes/auth/mcp/client-metadata.ts index fb133bc0e..3be5d8b85 100644 --- a/src/routes/auth/mcp/client-metadata.ts +++ b/src/routes/auth/mcp/client-metadata.ts @@ -1,5 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' -import { handleMcpOAuth } from '~/chat/server/mcp-oauth-route.server' +import { forwardWorkerRequest } from '~/server/runtime/worker-service.server' + export const Route = createFileRoute('/auth/mcp/client-metadata')({ - server: { handlers: { GET: ({ request }) => handleMcpOAuth(request) } }, + server: { + handlers: { + GET: ({ request }) => forwardWorkerRequest('CHAT', request), + }, + }, }) diff --git a/src/routes/chat.c.$conversationId.tsx b/src/routes/chat.c.$conversationId.tsx index 19c1e3718..9f0b8ff3d 100644 --- a/src/routes/chat.c.$conversationId.tsx +++ b/src/routes/chat.c.$conversationId.tsx @@ -1,5 +1,5 @@ import { createFileRoute, stripSearchParams } from '@tanstack/react-router' -import { App } from '~/chat/components/App' +import { AppRoute } from '~/chat/components/AppRoute' import { defaultWorkspaceSearch, validateWorkspaceSearch, @@ -8,5 +8,5 @@ import { export const Route = createFileRoute('/chat/c/$conversationId')({ validateSearch: validateWorkspaceSearch, search: { middlewares: [stripSearchParams(defaultWorkspaceSearch)] }, - component: App, + component: AppRoute, }) diff --git a/src/routes/chat.charts.tsx b/src/routes/chat.charts.tsx index 3fc691889..365dcae8c 100644 --- a/src/routes/chat.charts.tsx +++ b/src/routes/chat.charts.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { createClientOnlyFn } from '@tanstack/react-start' import { ClientOnly, createFileRoute } from '@tanstack/react-router' import { BuilderEmbeddedSkeleton, @@ -7,10 +8,12 @@ import { import { seo } from '~/utils/seo' import { webContainerHeaders } from '~/utils/stackblitz-embed' -const LazyChartsBuilderPage = React.lazy(() => - import('~/components/charts/ChartsBuilderPage.client').then((module) => ({ - default: module.ChartsBuilderPage, - })), +const LazyChartsBuilderPage = React.lazy( + createClientOnlyFn(() => + import('~/components/charts/ChartsBuilderPage.client').then((module) => ({ + default: module.ChartsBuilderPage, + })), + ), ) export const Route = createFileRoute('/chat/charts')({ diff --git a/src/routes/chat.new-project.tsx b/src/routes/chat.new-project.tsx index a68aa1513..89fe8c6e3 100644 --- a/src/routes/chat.new-project.tsx +++ b/src/routes/chat.new-project.tsx @@ -1,7 +1,6 @@ import { createFileRoute, redirect } from '@tanstack/react-router' import * as v from 'valibot' import { getCurrentUser } from '~/utils/auth.functions' -import { loadChatWorkspace } from '~/chat/workspace.functions' import { defaultWorkspaceSearch } from '~/chat/core/navigation' export const Route = createFileRoute('/chat/new-project')({ @@ -9,6 +8,7 @@ export const Route = createFileRoute('/chat/new-project')({ template: v.optional(v.pipe(v.string(), v.minLength(1), v.maxLength(200))), }), beforeLoad: async ({ search }) => { + const { loadChatWorkspace } = await import('~/chat/workspace.functions') const user = await getCurrentUser() if (!user) { const query = search.template diff --git a/src/routes/chat.p.$hash.tsx b/src/routes/chat.p.$hash.tsx index 68ef23a68..7cb3a4495 100644 --- a/src/routes/chat.p.$hash.tsx +++ b/src/routes/chat.p.$hash.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { createClientOnlyFn } from '@tanstack/react-start' import { ClientOnly, createFileRoute } from '@tanstack/react-router' import { BuilderEmbeddedSkeleton, @@ -7,10 +8,12 @@ import { import { seo } from '~/utils/seo' import { webContainerHeaders } from '~/utils/stackblitz-embed' -const LazySharedExamplePage = React.lazy(() => - import('~/components/examples/SharedExamplePage.client').then((module) => ({ - default: module.SharedExamplePage, - })), +const LazySharedExamplePage = React.lazy( + createClientOnlyFn(() => + import('~/components/examples/SharedExamplePage.client').then((module) => ({ + default: module.SharedExamplePage, + })), + ), ) export const Route = createFileRoute('/chat/p/$hash')({ diff --git a/src/routes/chat.project.$projectId.tsx b/src/routes/chat.project.$projectId.tsx index 1597815fd..6dee7a108 100644 --- a/src/routes/chat.project.$projectId.tsx +++ b/src/routes/chat.project.$projectId.tsx @@ -1,11 +1,11 @@ import { createFileRoute, notFound, redirect } from '@tanstack/react-router' import * as v from 'valibot' import { getCurrentUser } from '~/utils/auth.functions' -import { loadChatWorkspace } from '~/chat/workspace.functions' import { defaultWorkspaceSearch } from '~/chat/core/navigation' export const Route = createFileRoute('/chat/project/$projectId')({ beforeLoad: async ({ params }) => { + const { loadChatWorkspace } = await import('~/chat/workspace.functions') const parsed = v.safeParse(v.pipe(v.string(), v.uuid()), params.projectId) if (!parsed.success) throw notFound() const user = await getCurrentUser() diff --git a/src/routes/chat.shared.tsx b/src/routes/chat.shared.tsx index d8aafd0bd..878a78313 100644 --- a/src/routes/chat.shared.tsx +++ b/src/routes/chat.shared.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { createClientOnlyFn } from '@tanstack/react-start' import { ClientOnly, createFileRoute } from '@tanstack/react-router' import { BuilderEmbeddedSkeleton, @@ -7,10 +8,12 @@ import { import { seo } from '~/utils/seo' import { webContainerHeaders } from '~/utils/stackblitz-embed' -const LazySharedExamplePage = React.lazy(() => - import('~/components/examples/SharedExamplePage.client').then((module) => ({ - default: module.SharedExamplePage, - })), +const LazySharedExamplePage = React.lazy( + createClientOnlyFn(() => + import('~/components/examples/SharedExamplePage.client').then((module) => ({ + default: module.SharedExamplePage, + })), + ), ) export const Route = createFileRoute('/chat/shared')({ diff --git a/src/routes/chat.tsx b/src/routes/chat.tsx index eade2280a..242e9f220 100644 --- a/src/routes/chat.tsx +++ b/src/routes/chat.tsx @@ -1,9 +1,6 @@ -import { getChatAccess } from '~/chat/access.functions' import { createServerFn } from '@tanstack/react-start' -import { appearanceScript } from '~/chat/core/appearance-bootstrap' import { createFileRoute, redirect } from '@tanstack/react-router' -import { ChatShell } from '~/chat/components/ChatShell' -import chatStyles from '~/chat/styles.css?url' +import { ChatShellRoute } from '~/chat/components/ChatShellRoute' import { webContainerHeaders } from '~/utils/stackblitz-embed' const initialAppearance = createServerFn({ method: 'GET' }).handler( @@ -18,11 +15,20 @@ export const Route = createFileRoute('/chat')({ staticData: { showNavbar: false }, beforeLoad: async ({ cause }) => { if (cause === 'stay') return + const { getChatAccess } = await import('~/chat/access.functions') const access = await getChatAccess() if (!access?.unlocked) throw redirect({ to: '/chat-access', search: { invite: undefined } }) }, - loader: () => (typeof window === 'undefined' ? initialAppearance() : null), + loader: async () => { + const [{ appearanceScript }, { default: chatStyles }, appearance] = + await Promise.all([ + import('~/chat/core/appearance-bootstrap'), + import('~/chat/styles.css?url'), + typeof window === 'undefined' ? initialAppearance() : null, + ]) + return { appearanceScript, chatStyles, appearance } + }, staleTime: Infinity, shouldReload: false, headers: () => ({ @@ -31,12 +37,12 @@ export const Route = createFileRoute('/chat')({ 'Cloudflare-CDN-Cache-Control': 'no-store', }), head: ({ loaderData }) => ({ - links: [{ rel: 'stylesheet', href: chatStyles }], + links: [{ rel: 'stylesheet', href: loaderData?.chatStyles }], scripts: [ { - children: `${loaderData ? `document.documentElement.setAttribute('data-gum-ssr-appearance',${JSON.stringify(loaderData).replace(/ { + const { resolveChatRoute } = await import('~/chat/workspace.functions') const identity = await resolveChatRoute({ data: { ...params, conversationId: search.conversation }, }) diff --git a/src/routes/chat.w.$workspaceId.tsx b/src/routes/chat.w.$workspaceId.tsx index 4877fa94f..faa4e170b 100644 --- a/src/routes/chat.w.$workspaceId.tsx +++ b/src/routes/chat.w.$workspaceId.tsx @@ -1,5 +1,5 @@ import { createFileRoute, stripSearchParams } from '@tanstack/react-router' -import { App } from '~/chat/components/App' +import { AppRoute } from '~/chat/components/AppRoute' import { validateWorkspaceSearch, defaultWorkspaceSearch, @@ -8,5 +8,5 @@ import { export const Route = createFileRoute('/chat/w/$workspaceId')({ validateSearch: validateWorkspaceSearch, search: { middlewares: [stripSearchParams(defaultWorkspaceSearch)] }, - component: App, + component: AppRoute, }) diff --git a/src/routes/ds.iconography.lazy.tsx b/src/routes/ds.iconography.lazy.tsx index 5f810d914..85a2c678f 100644 --- a/src/routes/ds.iconography.lazy.tsx +++ b/src/routes/ds.iconography.lazy.tsx @@ -1,224 +1,21 @@ import * as React from 'react' -import { twMerge } from 'tailwind-merge' -import { createLazyFileRoute } from '@tanstack/react-router' -import { - CaretDownIcon, - CheckIcon, - MagnifyingGlassIcon, - StarIcon, -} from '@phosphor-icons/react' -import type { IconWeight } from '@phosphor-icons/react' -import { copyTextToClipboard } from '~/utils/browser-effects' -import { DsPage, DsSection } from '~/components/ds/DsKit' -import { phosphorIconRegistry } from '~/components/ds/phosphor-icons.generated' -import { - Dropdown, - DropdownContent, - DropdownItem, - DropdownTrigger, -} from '~/components/ds/ui' +import { createClientOnlyFn } from '@tanstack/react-start' +import { ClientOnly, createLazyFileRoute } from '@tanstack/react-router' + +const IconographyPage = React.lazy( + createClientOnlyFn(() => + import('~/components/ds/IconographyPage.client').then((module) => ({ + default: module.IconographyPage, + })), + ), +) export const Route = createLazyFileRoute('/ds/iconography')({ - component: IconographyPage, + component: () => ( + + + + + + ), }) - -const WEIGHTS: Array = [ - 'thin', - 'light', - 'regular', - 'bold', - 'fill', - 'duotone', -] -const SIZES = [16, 20, 24, 32, 48] -const PAGE_SIZE = 120 - -const triggerClass = - 'inline-flex h-10 items-center gap-2 rounded-lg border border-border-default bg-background-surface px-3 text-sm text-text-primary transition-colors hover:bg-background-subtle' - -// A small square that scales with the icon size — the visual size cue. -function SizeGlyph({ px }: { px: number }) { - const box = Math.max(8, Math.min(22, Math.round(px * 0.5))) - return ( - - ) -} - -function IconographyPage() { - const [weight, setWeight] = React.useState('regular') - const [size, setSize] = React.useState(24) - const [query, setQuery] = React.useState('') - const [searchOpen, setSearchOpen] = React.useState(false) - const [copied, setCopied] = React.useState(null) - const [visible, setVisible] = React.useState(PAGE_SIZE) - const searchInputRef = React.useRef(null) - - React.useEffect(() => { - if (searchOpen) searchInputRef.current?.focus() - }, [searchOpen]) - - const closeSearch = React.useCallback(() => { - setQuery('') - setSearchOpen(false) - }, []) - - const filtered = React.useMemo(() => { - const q = query.trim().toLowerCase() - if (!q) return phosphorIconRegistry - return phosphorIconRegistry.filter((icon) => - icon.name.toLowerCase().includes(q), - ) - }, [query]) - - // Reset paging when the result set changes. - React.useEffect(() => { - setVisible(PAGE_SIZE) - }, [query]) - - const shown = filtered.slice(0, visible) - - // The registry keys icons by their bare name, but `NameIcon` is the export - // callers actually import — copy and advertise that one. - const handleCopy = React.useCallback(async (name: string) => { - await copyTextToClipboard(`<${name}Icon />`) - setCopied(name) - window.setTimeout(() => setCopied(null), 1200) - }, []) - - return ( - - -
- {/* Style (weight) — dropdown with a live preview per weight */} - - - - - - {WEIGHTS.map((w) => ( - setWeight(w)}> - - {w} - {w === weight ? ( - - ) : null} - - ))} - - - - {/* Size — dropdown with a scaling size glyph per option */} - - - - - - {SIZES.map((s) => ( - setSize(s)}> - - - - {s}px - {s === size ? ( - - ) : null} - - ))} - - - - {/* Expanding search — icon grows on hover, opens into a field */} -
- - setQuery(e.target.value)} - onKeyDown={(e) => e.key === 'Escape' && closeSearch()} - placeholder="Search icons…" - className={twMerge( - 'min-w-0 flex-1 bg-transparent pr-3 text-sm text-text-primary placeholder-text-muted outline-none transition-opacity duration-200', - searchOpen ? 'opacity-100' : 'pointer-events-none opacity-0', - )} - /> -
- - - {filtered.length.toLocaleString()} icons - -
- -
- {shown.map(({ name, Cmp }) => ( - - ))} -
- - {shown.length < filtered.length ? ( -
- -
- ) : null} -
-
- ) -} diff --git a/src/server.ts b/src/server.ts index a802998f6..c52e22a04 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,14 +1,13 @@ +import { + timeRequestWork, + withDocsRequestTimings, +} from './utils/request-timing.server' +import { finalizeDocsHtmlHeaders } from './utils/docs-html-headers.server' import './instrument.server.mjs' -export { StreamObject } from '@durable-streams/server-cloudflare' -export { WorkspaceSync } from './chat/server/workspace-sync-object' -export { Conversation } from './chat/server/conversation' -export { TanChatWorkflow } from './chat/server/workflow-driver' - import { wrapFetchWithSentry } from '@sentry/tanstackstart-react' import handler, { createServerEntry } from '@tanstack/react-start/server-entry' import { runWithDatabaseContext, runWithDatabaseRequest } from '~/db/client' -import { runScheduledTasks } from '~/server/scheduled.server' import { runWithHostRuntimeContext, runWithHostRuntimeEnv, @@ -26,7 +25,6 @@ import { redirectInsecureSiteRequest, } from '~/utils/http' import { isFrameEmbeddingAllowed } from '~/utils/frame-embedding' -import { redirectChatOrigin } from './chat/server/origin-redirect' const SECURITY_HEADERS = { 'X-Frame-Options': 'DENY', @@ -80,6 +78,8 @@ function shouldRewriteDocsRequestToMarkdown(request: Request, url: URL) { function applyHostingHeaders(response: Response, url: URL) { const headers = new Headers(response.headers) + if (import.meta.env.MODE === 'staging') + headers.set('X-Robots-Tag', 'noindex, nofollow') for (const [key, value] of Object.entries(SECURITY_HEADERS)) { headers.set(key, value) @@ -261,9 +261,11 @@ const server = createServerEntry( return applyHostingHeaders(markdownResponse, url) } - const response = await handler.fetch(request, { - responseLinkHeader: STATIC_RESPONSE_LINK_HEADERS, - }) + const response = await timeRequestWork('start_handler', () => + handler.fetch(request, { + responseLinkHeader: STATIC_RESPONSE_LINK_HEADERS, + }), + ) const hostedResponse = applyHostingHeaders(response, url) logRequestEnd(context, response.status) @@ -279,13 +281,26 @@ const server = createServerEntry( ) export default { - fetch(request: Request, env: unknown, context: unknown) { + async fetch(request: Request, env: unknown, context: unknown) { + if ( + import.meta.env.MODE === 'staging' && + new URL(request.url).pathname.startsWith('/_a/') + ) + return new Response(null, { status: 204 }) const secureSite = redirectInsecureSiteRequest(request) if (secureSite) return secureSite - const canonicalChat = redirectChatOrigin(request) - if (canonicalChat) return canonicalChat + if (new URL(request.url).hostname === 'chat.tanstack.com') { + const { redirectChatOrigin } = + await import('./chat/server/origin-redirect') + const canonicalChat = redirectChatOrigin(request) + if (canonicalChat) return canonicalChat + } return runWithHostRuntimeEnv(env, () => - runWithHostRuntimeContext(context, () => server.fetch(request)), + runWithHostRuntimeContext(context, () => + withDocsRequestTimings(request, async () => + finalizeDocsHtmlHeaders(request, await server.fetch(request)), + ), + ), ) }, scheduled( @@ -296,9 +311,11 @@ export default { context.waitUntil( runWithHostRuntimeEnv(env, () => runWithHostRuntimeContext(context, () => - runWithDatabaseContext(() => - runScheduledTasks(controller.cron, controller.scheduledTime), - ), + runWithDatabaseContext(async () => { + const { runScheduledTasks } = + await import('~/server/scheduled.server') + return runScheduledTasks(controller.cron, controller.scheduledTime) + }), ), ), ) diff --git a/src/server/og/og-http.server.ts b/src/server/og/og-http.server.ts new file mode 100644 index 000000000..8337b2919 --- /dev/null +++ b/src/server/og/og-http.server.ts @@ -0,0 +1,56 @@ +type GenerateOgImageResponse = + (typeof import('~/server/og/generate.server'))['generateOgImageResponse'] + +const CACHE_HEADERS = { + 'Cache-Control': 'public, max-age=3600', + 'Cloudflare-CDN-Cache-Control': + 'public, max-age=86400, stale-while-revalidate=604800', +} as const + +export const handlers = { + GET: async ({ + request, + params, + }: { + request: Request + params: { _splat: string } + }) => { + const rawParam = params._splat + const libraryId = rawParam.replace(/\.png$/, '') + + const url = new URL(request.url) + let result: Awaited> + try { + const { generateOgImageResponse } = + await import('~/server/og/generate.server') + result = await generateOgImageResponse( + { + libraryId, + requestUrl: request.url, + title: url.searchParams.get('title') ?? undefined, + description: url.searchParams.get('description') ?? undefined, + }, + { headers: CACHE_HEADERS }, + ) + } catch (error) { + console.error('Failed to construct OG response', error) + return new Response('Failed to generate OG image', { status: 500 }) + } + + if ('kind' in result) { + return new Response(`Unknown library: ${libraryId}`, { status: 404 }) + } + + // ImageResponse builds the Response synchronously and renders inside + // a ReadableStream. Await the ready promise so render errors surface + // as 500s instead of an empty 200 cached at the edge. + try { + await result.ready + } catch (error) { + console.error('Failed to generate OG image', error) + return new Response('Failed to generate OG image', { status: 500 }) + } + + return result + }, +} diff --git a/src/server/og/readme-http.server.ts b/src/server/og/readme-http.server.ts new file mode 100644 index 000000000..c4f8df0b0 --- /dev/null +++ b/src/server/og/readme-http.server.ts @@ -0,0 +1,92 @@ +import { findLibrary } from '~/libraries' +import { OG_THEMES, isOgTheme } from '~/server/og/colors' +type GenerateReadmeHeaderResponse = + (typeof import('~/server/og/generate.server'))['generateReadmeHeaderResponse'] + +const CACHE_HEADERS = { + 'Cache-Control': 'public, max-age=3600', + 'Cloudflare-CDN-Cache-Control': + 'public, max-age=86400, stale-while-revalidate=604800', +} as const + +export const handlers = { + GET: async ({ + request, + params, + }: { + request: Request + params: { _splat: string } + }) => { + const libraryId = params._splat.replace(/\.png$/, '') + const library = findLibrary(libraryId) + if (!library) { + return new Response(`Unknown library: ${libraryId}`, { status: 404 }) + } + + const url = new URL(request.url) + + // Validated rather than ignored: a typo'd framework in a README should + // surface as a broken image, not as a banner naming the wrong package. + // An omitted param means "no framework"; a present-but-blank one + // (`?framework=`) is a malformed URL, not a request for the default. + const frameworkParam = url.searchParams.get('framework') ?? undefined + const framework = library.frameworks.find( + (value) => value === frameworkParam, + ) + if (frameworkParam !== undefined && framework === undefined) { + return new Response( + `Unknown framework "${frameworkParam}" for ${library.name}. Expected one of: ${library.frameworks.join(', ')}`, + { status: 400 }, + ) + } + + // Same contract as `framework`: supplied means it must be valid. + const themeParam = url.searchParams.get('theme') ?? undefined + if (themeParam !== undefined && !isOgTheme(themeParam)) { + return new Response( + `Unknown theme "${themeParam}". Expected one of: ${OG_THEMES.join(', ')}`, + { status: 400 }, + ) + } + + let result: Awaited> + try { + const { generateReadmeHeaderResponse } = + await import('~/server/og/generate.server') + result = await generateReadmeHeaderResponse( + { + libraryId, + requestUrl: request.url, + framework, + title: url.searchParams.get('title') ?? undefined, + subtitle: url.searchParams.get('subtitle') ?? undefined, + theme: themeParam, + }, + { headers: CACHE_HEADERS }, + ) + } catch (error) { + console.error('Failed to construct README header response', error) + return new Response('Failed to generate README header', { + status: 500, + }) + } + + if ('kind' in result) { + return new Response(`Unknown library: ${libraryId}`, { status: 404 }) + } + + // ImageResponse builds the Response synchronously and renders inside + // a ReadableStream. Await the ready promise so render errors surface + // as 500s instead of an empty 200 cached at the edge. + try { + await result.ready + } catch (error) { + console.error('Failed to generate README header', error) + return new Response('Failed to generate README header', { + status: 500, + }) + } + + return result + }, +} diff --git a/src/server/runtime/http-route.server.ts b/src/server/runtime/http-route.server.ts new file mode 100644 index 000000000..7144aa93f --- /dev/null +++ b/src/server/runtime/http-route.server.ts @@ -0,0 +1,50 @@ +import { z } from 'zod' + +// URLPattern is native in workerd, including local development and preview. +declare const URLPattern: typeof import('@cloudflare/workers-types').URLPattern + +type HttpContext = { request: Request; params: TParams } +type HttpHandler = ( + context: HttpContext, +) => Response | Promise + +export function defineHttpRoute>( + path: string, + paramsSchema: z.ZodType, + handlers: Record>, +) { + const pattern = new URLPattern({ + pathname: path + .split('/') + .map((segment) => + segment === '$' + ? ':_splat*' + : segment.startsWith('$') + ? `:${segment.slice(1)}` + : segment, + ) + .join('/'), + }) + return { + path, + match(request: Request) { + const url = new URL(request.url) + const match = pattern.exec({ + pathname: url.pathname.replace(/\/+$/, '') || '/', + }) + if (!match) return + const params: Record = {} + for (const [key, value] of Object.entries(match.pathname.groups)) { + params[key] = decodeURIComponent(value ?? '') + } + const method = request.method === 'HEAD' ? 'GET' : request.method + const handler = handlers[method] + const context = { request, params: paramsSchema.parse(params) } + return { + handler: handler ? () => handler(context) : undefined, + context, + allow: Object.keys(handlers).join(', '), + } + }, + } +} diff --git a/src/server/runtime/worker-service.server.ts b/src/server/runtime/worker-service.server.ts new file mode 100644 index 000000000..a917b76e5 --- /dev/null +++ b/src/server/runtime/worker-service.server.ts @@ -0,0 +1,25 @@ +import { getHostRuntimeEnv } from './host.server' + +type WorkerService = { + fetch(request: Request): Promise +} + +function isWorkerService(value: unknown): value is WorkerService { + return ( + typeof value === 'object' && + value !== null && + 'fetch' in value && + typeof value.fetch === 'function' + ) +} + +export async function forwardWorkerRequest( + name: 'OG' | 'CHAT' | 'STARTER', + request: Request, +) { + const env = await getHostRuntimeEnv() + const service = env?.[name] + if (!isWorkerService(service)) + throw new Error(`${name} Worker binding is missing`) + return service.fetch(request) +} diff --git a/src/server/scheduled.server.ts b/src/server/scheduled.server.ts index 960a04c8e..6d7526154 100644 --- a/src/server/scheduled.server.ts +++ b/src/server/scheduled.server.ts @@ -1,4 +1,3 @@ -import { recoverChatWorkspaceSync } from '~/chat/server/scheduled.server' import { materializeWorkflowSchedules } from '@tanstack/workflow-runtime' import { pruneStaleCacheRows } from '~/utils/github-content-cache.server' import { refreshHomepageNpmStatsSummary } from '~/utils/homepage-npm-stats.server' @@ -34,10 +33,7 @@ export async function runScheduledTasks(cron: string, scheduledTime: number) { ]) return case WORKFLOW_SWEEP_CRON: - await Promise.all([ - recoverChatWorkspaceSync(), - runWorkflowSweep(cron, scheduledTime), - ]) + await runWorkflowSweep(cron, scheduledTime) return default: console.warn(`[scheduled] No task registered for cron: ${cron}`) diff --git a/src/types/durable-streams.d.ts b/src/types/durable-streams.d.ts new file mode 100644 index 000000000..5793f4874 --- /dev/null +++ b/src/types/durable-streams.d.ts @@ -0,0 +1,10 @@ +import type { Request, Response } from '@cloudflare/workers-types' +import '@durable-streams/server-cloudflare' + +declare module '@durable-streams/server-cloudflare' { + // The package emits DOM Request types, but this entrypoint runs in workerd + // and accepts the platform's Request, including its Cloudflare properties. + interface StreamObject { + fetch(request: Request): Promise + } +} diff --git a/src/utils/analytics/providers/google.ts b/src/utils/analytics/providers/google.ts index d9bf78ff9..71da9f12b 100644 --- a/src/utils/analytics/providers/google.ts +++ b/src/utils/analytics/providers/google.ts @@ -9,7 +9,11 @@ declare global { export const googleAnalyticsProvider: AnalyticsProvider = { trackEvent(event, properties) { - if (typeof window === 'undefined' || !window.gtag) { + if ( + import.meta.env.MODE === 'staging' || + typeof window === 'undefined' || + !window.gtag + ) { return } diff --git a/src/utils/analytics/scarf.ts b/src/utils/analytics/scarf.ts index ad87346cf..e20553cdb 100644 --- a/src/utils/analytics/scarf.ts +++ b/src/utils/analytics/scarf.ts @@ -4,7 +4,11 @@ const SCARF_ENDPOINT = 'https://tanstack.gateway.scarf.sh/site-events' const CLIPBOARD_TEXT_LIMIT = 512 function sendScarfEvent(event: string, fields: Record) { - if (import.meta.env.DEV || typeof window === 'undefined') { + if ( + import.meta.env.DEV || + import.meta.env.MODE === 'staging' || + typeof window === 'undefined' + ) { return } @@ -67,7 +71,7 @@ export function trackScarfPageView(page: string) { } export function trackScarfClipboardText(event: 'copy' | 'paste', text: string) { - if (import.meta.env.DEV) return + if (import.meta.env.DEV || import.meta.env.MODE === 'staging') return try { navigator.sendBeacon( diff --git a/src/utils/docs-cache-headers.ts b/src/utils/docs-cache-headers.ts index e684afdca..22b50a99e 100644 --- a/src/utils/docs-cache-headers.ts +++ b/src/utils/docs-cache-headers.ts @@ -21,7 +21,14 @@ export function getDocsCacheHeaders({ version === library.latestVersion || version === library.latestBranch) const cacheTags = library - ? ['docs:all', `docs:${library.id}`, `docs:${library.id}:branch:${branch}`] + ? [ + 'docs:all', + `docs:${library.id}`, + `docs:${library.id}:branch:${branch}`, + 'docs-config:all', + `docs-config:${library.repo}`, + `docs-config:${library.repo}:${branch}`, + ] : ['docs:all'] return { diff --git a/src/utils/docs-html-headers.server.ts b/src/utils/docs-html-headers.server.ts new file mode 100644 index 000000000..60947f351 --- /dev/null +++ b/src/utils/docs-html-headers.server.ts @@ -0,0 +1,37 @@ +import { getDocsCacheHeaders } from './docs-cache-headers' + +// Called after loaders finish, so data-fetching functions cannot overwrite +// the page's cache policy or its document and sidebar invalidation tags. +export function finalizeDocsHtmlHeaders(request: Request, response: Response) { + if (!response.headers.has('X-Docs-Html')) return response + const headers = new Headers(response.headers) + headers.delete('X-Docs-Html') + if ( + response.status === 200 && + response.headers.get('X-Docs-Html') === '1' && + !headers.has('set-cookie') && + !/no-store|private/i.test(headers.get('cache-control') ?? '') + ) { + const [, libraryId, version] = new URL(request.url).pathname.split('/') + for (const [key, value] of Object.entries( + getDocsCacheHeaders({ libraryId, version }), + )) { + headers.set(key, value) + } + } else { + headers.set('Cache-Control', 'private, no-store') + headers.set('Cloudflare-CDN-Cache-Control', 'no-store') + } + // Keep complete HTML responses out of every shared cache during profiling. + headers.set('Cache-Control', 'private, no-store') + headers.set('Cloudflare-CDN-Cache-Control', 'no-store') + headers.set( + 'Vary', + [headers.get('Vary'), 'Cookie', 'Authorization'].filter(Boolean).join(', '), + ) + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers, + }) +} diff --git a/src/utils/docs.functions.ts b/src/utils/docs.functions.ts index b0f40250e..25e88633e 100644 --- a/src/utils/docs.functions.ts +++ b/src/utils/docs.functions.ts @@ -449,6 +449,7 @@ export const fetchDocs = createServerFn({ method: 'GET' }) return { content: frontMatter.content, + isFallback: result.isFallback, title: frontMatter.data?.title ?? 'Content temporarily unavailable', description, keywords, diff --git a/src/utils/docs.ts b/src/utils/docs.ts index f69b5c586..9c20f4921 100644 --- a/src/utils/docs.ts +++ b/src/utils/docs.ts @@ -1,4 +1,6 @@ import { isNotFound, notFound } from '@tanstack/react-router' +import type { QueryClient } from '@tanstack/react-query' +import { docsPathManifestQueryOptions } from '~/queries/docsManifest' import { fetchDocs, fetchDocsManifest, @@ -82,6 +84,7 @@ export async function getDocsManifest(opts: { } export async function resolveDocsRoutePath(opts: { + queryClient?: QueryClient branch: string defaultDocs: string docsPath: string @@ -95,13 +98,14 @@ export async function resolveDocsRoutePath(opts: { return defaultDocsResolution } - const manifest = await fetchDocsPathManifest({ - data: { - repo: opts.repo, - branch: opts.branch, - docsRoot: opts.docsRoot, - }, - }) + const request = { + repo: opts.repo, + branch: opts.branch, + docsRoot: opts.docsRoot, + } + const manifest = opts.queryClient + ? await opts.queryClient.fetchQuery(docsPathManifestQueryOptions(request)) + : await fetchDocsPathManifest({ data: request }) if (manifest.paths.length === 0) { return { type: 'render', docsPath: opts.docsPath } @@ -175,6 +179,7 @@ export type LoadDocsRouteResult = } export async function loadDocsRoute(opts: { + queryClient?: QueryClient branch: string defaultDocs: string docsPath: string @@ -232,6 +237,7 @@ export async function loadDocsRoute(opts: { */ async function findLatestDocsPath( opts: { + queryClient?: QueryClient branch: string docsRoot: string latestBranch?: string @@ -244,13 +250,14 @@ async function findLatestDocsPath( } try { - const manifest = await fetchDocsPathManifest({ - data: { - repo: opts.repo, - branch: opts.latestBranch, - docsRoot: opts.docsRoot, - }, - }) + const request = { + repo: opts.repo, + branch: opts.latestBranch, + docsRoot: opts.docsRoot, + } + const manifest = opts.queryClient + ? await opts.queryClient.fetchQuery(docsPathManifestQueryOptions(request)) + : await fetchDocsPathManifest({ data: request }) return docsManifestHasPath(manifest, docsPath) ? docsPath : null } catch { @@ -259,6 +266,7 @@ async function findLatestDocsPath( } async function resolveDocsRoutePathWithRedirects(opts: { + queryClient?: QueryClient branch: string defaultDocs: string docsPath: string diff --git a/src/utils/github-content-cache.server.ts b/src/utils/github-content-cache.server.ts index 7fc248527..051cf3912 100644 --- a/src/utils/github-content-cache.server.ts +++ b/src/utils/github-content-cache.server.ts @@ -1,3 +1,4 @@ +import { timeRequestWork } from './request-timing.server' import { getBlobStorage, getBlobStorageCache, @@ -561,7 +562,9 @@ async function readBlob( cache: BlobStorageCache | undefined, key: string, ) { - const cached = await cache?.get(key) + const cached = await timeRequestWork('content_edge_read', async () => + cache?.get(key), + ) const cachedBlob = cached ? getStoredBlob({ key, metadata: cached.metadata, text: cached.text }) : undefined @@ -570,13 +573,15 @@ async function readBlob( return cachedBlob } - const object = await storage.get(key) + const object = await timeRequestWork('content_r2_read', () => + storage.get(key), + ) if (!object) { return undefined } - const text = await object.text() + const text = await timeRequestWork('content_r2_body', () => object.text()) const stored = getStoredBlob({ key, metadata: object.metadata, @@ -588,10 +593,12 @@ async function readBlob( return undefined } - await writeBlobCache(cache, key, { - metadata: stored.metadata, - text: stored.text, - }) + await timeRequestWork('content_edge_write', () => + writeBlobCache(cache, key, { + metadata: stored.metadata, + text: stored.text, + }), + ) return stored } @@ -1287,34 +1294,52 @@ async function getCachedGitHubContent(opts: { } } - return withPendingRefresh(opts.cacheKey, async () => { - const latestRow = await readRow() - const latestValue = opts.readStoredValue(latestRow) - const latestForciblyStale = - !!latestRow && isForciblyStale(latestRow.staleAt) - - if ( - canUseStoredGithubContentValue(opts.contentKind, latestValue) && - latestRow && - !latestForciblyStale && - isFresh(latestRow.staleAt) - ) { - return latestValue - } + const refresh = () => + withPendingRefresh(opts.cacheKey, async () => { + const latestRow = await readRow() + const latestValue = opts.readStoredValue(latestRow) + const latestForciblyStale = + !!latestRow && isForciblyStale(latestRow.staleAt) - try { - const value = await opts.origin() - await persist(value) - return value - } catch (error) { - if (latestValue !== undefined && latestValue !== null) { - console.warn(`[GitHub Cache] Serving stale value ${opts.cacheKey}`) + if ( + canUseStoredGithubContentValue(opts.contentKind, latestValue) && + latestRow && + !latestForciblyStale && + isFresh(latestRow.staleAt) + ) { return latestValue } - throw error + try { + const value = await timeRequestWork('content_origin', opts.origin) + await timeRequestWork('content_persist', () => persist(value)) + return value + } catch (error) { + if (latestValue !== undefined && latestValue !== null) { + console.warn(`[GitHub Cache] Serving stale value ${opts.cacheKey}`) + return latestValue + } + + throw error + } + }) + // Explicit invalidations and negative entries still refresh before returning. + if (storedValue !== undefined && storedValue !== null && !forciblyStale) { + if ( + scheduleHostRuntimeTask(() => + refresh().catch((error) => { + console.warn( + `[GitHub Cache] Background content refresh failed ${opts.cacheKey}`, + error, + ) + }), + ) + ) { + return storedValue } - }) + } + + return refresh() } async function upsertDocsArtifact(opts: { @@ -1424,7 +1449,7 @@ export async function getCachedDocsArtifact(opts: { } try { - const payload = await opts.build() + const payload = await timeRequestWork('artifact_build', opts.build) await upsertDocsArtifact({ ...opts, payload }) return payload } catch (error) { diff --git a/src/utils/request-timing.server.ts b/src/utils/request-timing.server.ts new file mode 100644 index 000000000..c44ebf56a --- /dev/null +++ b/src/utils/request-timing.server.ts @@ -0,0 +1,136 @@ +import { AsyncLocalStorage } from 'node:async_hooks' +import * as Sentry from '@sentry/tanstackstart-react' + +// On deployed Workers this clock advances only on I/O. These spans locate +// waits; platform CPU/wall metrics are required to measure CPU-only work. +// https://developers.cloudflare.com/workers/runtime-apis/performance/ +type Timing = { name: string; startMs: number; durationMs: number } +type Timings = { + startedAt: number + entries: Array + databaseUsed: boolean +} +const storage = new AsyncLocalStorage() +let instanceId: string | undefined +let docsRequests = 0 + +export function markRequestDatabaseUse() { + const timings = storage.getStore() + if (timings) timings.databaseUsed = true +} + +export async function timeRequestWork( + name: string, + work: () => T | Promise, +): Promise { + const timings = storage.getStore() + if (!timings) return work() + const startedAt = performance.now() + try { + return await Sentry.startSpan({ name, op: 'docs.server' }, work) + } finally { + timings.entries.push({ + name, + startMs: startedAt - timings.startedAt, + durationMs: performance.now() - startedAt, + }) + } +} + +export async function withDocsRequestTimings( + request: Request, + work: () => Promise, +) { + if (!/^\/[^/]+\/[^/]+\/docs\//.test(new URL(request.url).pathname)) + return work() + instanceId ??= crypto.randomUUID() + const workerInstance = instanceId + const docsRequest = ++docsRequests + const timings: Timings = { + startedAt: performance.now(), + entries: [], + databaseUsed: false, + } + return storage.run(timings, async () => { + const response = await work() + const durationMs = performance.now() - timings.startedAt + const totals = new Map() + for (const entry of timings.entries) + totals.set(entry.name, (totals.get(entry.name) ?? 0) + entry.durationMs) + const headers = new Headers(response.headers) + headers.set( + 'Server-Timing', + [ + `app_headers;dur=${durationMs.toFixed(1)}`, + ...Array.from( + totals, + ([name, duration]) => `${name};dur=${duration.toFixed(1)}`, + ), + `db_used;desc="${timings.databaseUsed}"`, + `docs_request;desc="${docsRequest}"`, + ].join(', '), + ) + console.log( + JSON.stringify({ + logType: 'docs_timing', + workerInstance, + docsRequest, + phase: 'headers_ready', + pathname: new URL(request.url).pathname, + ray: request.headers.get('cf-ray'), + status: response.status, + headersReadyMs: durationMs, + databaseUsed: timings.databaseUsed, + phases: timings.entries, + }), + ) + // This measures work through response creation, not streamed body completion. + const reader = response.body?.getReader() + let firstByte = true + const logBodyPhase = (phase: string) => + console.log( + JSON.stringify({ + logType: 'docs_timing', + workerInstance, + docsRequest, + phase, + pathname: new URL(request.url).pathname, + ray: request.headers.get('cf-ray'), + elapsedMs: performance.now() - timings.startedAt, + databaseUsed: timings.databaseUsed, + phases: timings.entries, + }), + ) + const body = reader + ? new ReadableStream({ + async pull(controller) { + try { + const result = await reader.read() + if (result.done) { + logBodyPhase('body_complete') + controller.close() + } else { + if (firstByte) { + firstByte = false + logBodyPhase('first_body_byte') + } + controller.enqueue(result.value) + } + } catch (error) { + logBodyPhase('body_error') + controller.error(error) + } + }, + async cancel(reason) { + logBodyPhase('body_cancelled') + await reader.cancel(reason) + }, + }) + : null + return new Response(body, { + status: response.status, + statusText: response.statusText, + headers, + }) + }) +} diff --git a/src/utils/sentry.server.ts b/src/utils/sentry.server.ts index 014a6f49c..9422c6b31 100644 --- a/src/utils/sentry.server.ts +++ b/src/utils/sentry.server.ts @@ -9,7 +9,9 @@ export function initSentryServer(): void { Sentry.init({ dsn: env.SENTRY_DSN ?? SENTRY_DSN, - enabled: process.env.NODE_ENV === 'production' || Boolean(env.SENTRY_DSN), + enabled: + import.meta.env.MODE !== 'staging' && + (process.env.NODE_ENV === 'production' || Boolean(env.SENTRY_DSN)), tracesSampleRate: 1.0, environment: process.env.NODE_ENV ?? 'development', }) diff --git a/src/workers/chat.ts b/src/workers/chat.ts new file mode 100644 index 000000000..bc4796709 --- /dev/null +++ b/src/workers/chat.ts @@ -0,0 +1,49 @@ +import { chatHttpRoutes } from '~/chat/server/http/routes' +import { recoverChatWorkspaceSync } from '~/chat/server/scheduled.server' +import { runWithDatabaseContext, runWithDatabaseRequest } from '~/db/client' +import { + runWithHostRuntimeContext, + runWithHostRuntimeEnv, +} from '~/server/runtime/host.server' + +export { + StreamObject, + WorkspaceSync, + Conversation, + TanChatWorkflow, +} from '~/chat/server/entries' + +export default { + fetch(request: Request, env: unknown, context: unknown) { + return runWithHostRuntimeEnv(env, () => + runWithHostRuntimeContext(context, () => + runWithDatabaseRequest(async () => { + for (const route of chatHttpRoutes) { + const match = route.match(request) + if (!match) continue + if (!match.handler) + return new Response('Method not allowed', { + status: 405, + headers: { Allow: match.allow }, + }) + return match.handler() + } + return new Response('Not found', { status: 404 }) + }), + ), + ) + }, + scheduled( + _controller: unknown, + env: unknown, + context: { waitUntil(promise: Promise): void }, + ) { + context.waitUntil( + runWithHostRuntimeEnv(env, () => + runWithHostRuntimeContext(context, () => + runWithDatabaseContext(() => recoverChatWorkspaceSync()), + ), + ), + ) + }, +} diff --git a/src/workers/og.ts b/src/workers/og.ts new file mode 100644 index 000000000..10c6e37dd --- /dev/null +++ b/src/workers/og.ts @@ -0,0 +1,25 @@ +import { handlers as og } from '~/server/og/og-http.server' +import { handlers as readme } from '~/server/og/readme-http.server' +import { runWithHostRuntimeEnv } from '~/server/runtime/host.server' + +export default { + fetch(request: Request, env: unknown) { + return runWithHostRuntimeEnv(env, () => { + const match = /^\/api\/(og|readme)\/(.+)\.png$/.exec( + new URL(request.url).pathname, + ) + if (!match) return new Response('Not found', { status: 404 }) + if (request.method !== 'GET' && request.method !== 'HEAD') { + return new Response('Method not allowed', { + status: 405, + headers: { Allow: 'GET, HEAD' }, + }) + } + const handler = match[1] === 'og' ? og.GET : readme.GET + return handler({ + request, + params: { _splat: decodeURIComponent(match[2]) }, + }) + }) + }, +} diff --git a/src/workers/starter.ts b/src/workers/starter.ts new file mode 100644 index 000000000..9ac0be75b --- /dev/null +++ b/src/workers/starter.ts @@ -0,0 +1,28 @@ +import { starterHttpRoutes } from '~/application-starter/api/http/routes' +import { runWithDatabaseRequest } from '~/db/client' +import { + runWithHostRuntimeContext, + runWithHostRuntimeEnv, +} from '~/server/runtime/host.server' + +export default { + fetch(request: Request, env: unknown, context: unknown) { + return runWithHostRuntimeEnv(env, () => + runWithHostRuntimeContext(context, () => + runWithDatabaseRequest(async () => { + for (const route of starterHttpRoutes) { + const match = route.match(request) + if (!match) continue + if (!match.handler) + return new Response('Method not allowed', { + status: 405, + headers: { Allow: match.allow }, + }) + return match.handler() + } + return new Response('Not found', { status: 404 }) + }), + ), + ) + }, +} diff --git a/staging/README.md b/staging/README.md new file mode 100644 index 000000000..703babf1d --- /dev/null +++ b/staging/README.md @@ -0,0 +1,69 @@ +# Worker split staging + +This branch is a staging experiment. Production deployment commands are disabled. + +The split site is `https://tanstack-perf-stage-site.thetanstack.workers.dev`. +The unified comparison is `https://tanstack-perf-stage-baseline.thetanstack.workers.dev`. + +Both use only `tanstack-perf-stage-*` resources. No production secrets, database, +Hyperdrive, AI binding, custom domain, cron, or namespace transfer is configured. +Session and encryption keys were generated for staging. Signed-in chat and +project persistence are outside this public docs comparison. + +Build with `pnpm build:staging`, deploy with `pnpm deploy:staging`. +The deployment preflight checks every destination and resource binding before +uploading anything. It removes `WRANGLER_CI_OVERRIDE_NAME` and +`WRANGLER_CI_MATCH_TAG` from child Wrangler processes and supplies each name +explicitly. The installed Wrangler overrides even `--name` when that CI name +variable is present. + +The remote deployment is exercised with those CI variables set to staging +values. The production-name override case is tested offline. This reproduces +the relevant environment behavior, it is not a connected Cloudflare Builds job. + +`python3 scripts/measure-worker-staging.py` sends 80 alternating requests using +fresh curl connections. Both sites share a staging-only GitHub content cache. +The unified copy comes from main at `5cd9f45a`, with identical staging telemetry +suppression and request counters added. The candidate includes the earlier +docs optimizations as well as the split, so this compares the whole candidate, +not the split alone. + +A request counter of one means the first observed docs request in an instance. +It does not prove every Cloudflare cache is cold. Worker JavaScript timers also +do not measure platform preparation or CPU accurately. Report these limitations +alongside timing results. + +## Results + +All 80 requests returned 200 from DFW. Median TTFB in milliseconds: + +| Requests | Unified | Split | +| --- | ---: | ---: | +| First observed in an instance | 1,531 (12 samples) | 791 (11 samples) | +| Reused instance | 213 (28 samples) | 202 (29 samples) | +| All requests | 227 | 236 | + +First observed requests were 48% faster in this sample. Overall p95 fell from +1,978 ms to 1,186 ms. The overall median did not improve. This supports a cold +response benefit, not a claim that every docs request is faster. The sample is +small, from one location, with no database or signed-in users. Both Workers +reported `db_used=false`. Platform preparation and first SSR work remain mixed +in the first observed measurement. + +Measured versions: unified `5872b2b2-db2f-4720-a66f-ec23e5ab0562`, +split `f8a399c6-47b8-4fca-971e-ac002904f1eb`. A final deployment additionally +turns off browser Sentry reporting, it does not change the docs server flow. + +Rollback was exercised on the staging site and chat Workers, then the latest +versions were restored. Docs returned 200 and unauthenticated chat returned +401 after rollback. All six staging namespace IDs remained unchanged. This +checks code rollback with stable ownership, not reversing a state transfer. + +The browser check reached Installation using SPA navigation, with no page +errors or chat requests. OG and starter features returned 200. The full test +suite passed: 570 unit, 2,270 chat, and 20 desktop tests, plus type checks and +lint. Four unit and one desktop test were skipped. + +The unified comparison can be rebuilt from the pinned main commit with +`node scripts/build-worker-staging-baseline.mjs`. Its staging-only changes are +recorded in `baseline.patch`. diff --git a/staging/baseline.patch b/staging/baseline.patch new file mode 100644 index 000000000..3736a7f85 --- /dev/null +++ b/staging/baseline.patch @@ -0,0 +1,417 @@ +--- a/wrangler.jsonc ++++ b/wrangler.jsonc +@@ -1,86 +1,88 @@ + { + "$schema": "node_modules/wrangler/config-schema.json", +- "name": "tanstack-com", ++ "name": "tanstack-perf-stage-baseline", + "account_id": "8da95258a9c70b54c3e2b374a0079106", + "main": "src/server.ts", + "compatibility_date": "2026-06-19", +- "compatibility_flags": ["nodejs_compat"], ++ "compatibility_flags": [ ++ "nodejs_compat" ++ ], + "observability": { +- "enabled": true, ++ "enabled": true + }, + "cache": { +- "enabled": false, ++ "enabled": false + }, + "vars": { + "APP_MODE": "production", +- "MCP_EGRESS_MODE": "cloudflare-public", +- "KODY_ORIGIN": "https://kody.codes", +- "INCLUDED_MODEL": "@cf/moonshotai/kimi-k2.6", ++ "SENTRY_DSN": "" + }, + "limits": { +- "cpu_ms": 300000, ++ "cpu_ms": 300000 + }, + "durable_objects": { + "bindings": [ + { + "name": "STREAMS", +- "class_name": "StreamObject", +- "script_name": "tanstack-chat", ++ "class_name": "StreamObject" + }, + { + "name": "WORKSPACE_SYNC", +- "class_name": "WorkspaceSync", +- "script_name": "tanstack-chat", ++ "class_name": "WorkspaceSync" + }, + { + "name": "CONVERSATIONS", +- "class_name": "Conversation", +- "script_name": "tanstack-chat", +- }, +- ], ++ "class_name": "Conversation" ++ } ++ ] + }, + "exports": { +- "StreamObject": { "type": "durable-object", "storage": "sqlite" }, +- "WorkspaceSync": { "type": "durable-object", "storage": "sqlite" }, +- "Conversation": { "type": "durable-object", "storage": "sqlite" }, ++ "Conversation": { ++ "type": "durable-object", ++ "storage": "sqlite" ++ }, ++ "StreamObject": { ++ "type": "durable-object", ++ "storage": "sqlite" ++ }, ++ "WorkspaceSync": { ++ "type": "durable-object", ++ "storage": "sqlite" ++ } + }, + "workflows": [ + { +- "name": "tanstack-chat-workflows", ++ "name": "tanstack-perf-stage-baseline-workflow", + "binding": "WORKFLOW_RUNS", +- "class_name": "TanChatWorkflow", +- "script_name": "tanstack-chat", +- }, ++ "class_name": "TanChatWorkflow" ++ } + ], + "triggers": { +- "crons": ["* * * * *", "0 9 * * *", "0 */6 * * *"], ++ "crons": [] + }, + "assets": { +- "binding": "ASSETS", ++ "binding": "ASSETS" + }, +- "ai": { "binding": "AI" }, + "r2_buckets": [ + { + "binding": "GITHUB_CONTENT_CACHE", +- "bucket_name": "tanstack-github-content-cache", ++ "bucket_name": "tanstack-perf-stage-github" + }, + { + "binding": "NPM_DOWNLOAD_CACHE", +- "bucket_name": "tanstack-npm-download-cache", ++ "bucket_name": "tanstack-perf-stage-npm" + }, + { + "binding": "BUILDER_PROJECTS", +- "bucket_name": "tanstack-notebook-projects", ++ "bucket_name": "tanstack-perf-stage-files" + }, + { + "binding": "FILES", +- "bucket_name": "tanstack-notebook-projects", +- }, ++ "bucket_name": "tanstack-perf-stage-files" ++ } + ], +- "hyperdrive": [ +- { +- "binding": "HYPERDRIVE", +- "id": "6ec1513b87bb47e9897e5502727b0388", +- }, +- ], +-} ++ "hyperdrive": [], ++ "routes": [], ++ "workers_dev": true, ++ "preview_urls": false ++} +\ No newline at end of file +--- a/vite.config.ts ++++ b/vite.config.ts +@@ -225,7 +225,7 @@ + __TANSTACK_ENABLE_SERVER_BUILDER_GENERATION__: JSON.stringify(true), + __TANSTACK_ENABLE_IMAGE_TRANSFORMATIONS__: JSON.stringify(!isDev), + __TANSTACK_LOCAL_DOCS_TOKEN__: JSON.stringify(localDocsDevToken), +- __TANSTACK_SITE_URL__: JSON.stringify(SITE_URL), ++ __TANSTACK_SITE_URL__: JSON.stringify(mode === 'staging' ? 'https://tanstack-perf-stage-baseline.thetanstack.workers.dev' : SITE_URL), + }, + resolve: { + alias: { +--- a/src/server.ts ++++ b/src/server.ts +@@ -1,3 +1,4 @@ ++import { withDocsRequestTimings } from './utils/request-timing.server' + import './instrument.server.mjs' + + export { StreamObject } from '@durable-streams/server-cloudflare' +@@ -80,6 +81,7 @@ + + function applyHostingHeaders(response: Response, url: URL) { + const headers = new Headers(response.headers) ++ if (import.meta.env.MODE === "staging") headers.set("X-Robots-Tag", "noindex, nofollow") + + for (const [key, value] of Object.entries(SECURITY_HEADERS)) { + headers.set(key, value) +@@ -280,12 +282,13 @@ + + export default { + fetch(request: Request, env: unknown, context: unknown) { ++ if (import.meta.env.MODE === "staging" && new URL(request.url).pathname.startsWith("/_a/")) return new Response(null, { status: 204 }) + const secureSite = redirectInsecureSiteRequest(request) + if (secureSite) return secureSite + const canonicalChat = redirectChatOrigin(request) + if (canonicalChat) return canonicalChat + return runWithHostRuntimeEnv(env, () => +- runWithHostRuntimeContext(context, () => server.fetch(request)), ++ runWithHostRuntimeContext(context, () => withDocsRequestTimings(request, () => server.fetch(request))), + ) + }, + scheduled( +--- a/src/db/client.ts ++++ b/src/db/client.ts +@@ -1,3 +1,4 @@ ++import { markRequestDatabaseUse } from '../utils/request-timing.server' + import { drizzle } from 'drizzle-orm/postgres-js' + import postgres from 'postgres' + import { AsyncLocalStorage } from 'node:async_hooks' +@@ -63,6 +64,7 @@ + } + + function getDb() { ++ markRequestDatabaseUse() + const context = databaseStorage.getStore() + if (context) { + return getRequestDb(context) +--- /dev/null ++++ b/src/utils/request-timing.server.ts +@@ -0,0 +1,136 @@ ++import { AsyncLocalStorage } from 'node:async_hooks' ++import * as Sentry from '@sentry/tanstackstart-react' ++ ++// On deployed Workers this clock advances only on I/O. These spans locate ++// waits; platform CPU/wall metrics are required to measure CPU-only work. ++// https://developers.cloudflare.com/workers/runtime-apis/performance/ ++type Timing = { name: string; startMs: number; durationMs: number } ++type Timings = { ++ startedAt: number ++ entries: Array ++ databaseUsed: boolean ++} ++const storage = new AsyncLocalStorage() ++let instanceId: string | undefined ++let docsRequests = 0 ++ ++export function markRequestDatabaseUse() { ++ const timings = storage.getStore() ++ if (timings) timings.databaseUsed = true ++} ++ ++export async function timeRequestWork( ++ name: string, ++ work: () => T | Promise, ++): Promise { ++ const timings = storage.getStore() ++ if (!timings) return work() ++ const startedAt = performance.now() ++ try { ++ return await Sentry.startSpan({ name, op: 'docs.server' }, work) ++ } finally { ++ timings.entries.push({ ++ name, ++ startMs: startedAt - timings.startedAt, ++ durationMs: performance.now() - startedAt, ++ }) ++ } ++} ++ ++export async function withDocsRequestTimings( ++ request: Request, ++ work: () => Promise, ++) { ++ if (!/^\/[^/]+\/[^/]+\/docs\//.test(new URL(request.url).pathname)) ++ return work() ++ instanceId ??= crypto.randomUUID() ++ const workerInstance = instanceId ++ const docsRequest = ++docsRequests ++ const timings: Timings = { ++ startedAt: performance.now(), ++ entries: [], ++ databaseUsed: false, ++ } ++ return storage.run(timings, async () => { ++ const response = await work() ++ const durationMs = performance.now() - timings.startedAt ++ const totals = new Map() ++ for (const entry of timings.entries) ++ totals.set(entry.name, (totals.get(entry.name) ?? 0) + entry.durationMs) ++ const headers = new Headers(response.headers) ++ headers.set( ++ 'Server-Timing', ++ [ ++ `app_headers;dur=${durationMs.toFixed(1)}`, ++ ...Array.from( ++ totals, ++ ([name, duration]) => `${name};dur=${duration.toFixed(1)}`, ++ ), ++ `db_used;desc="${timings.databaseUsed}"`, ++ `docs_request;desc="${docsRequest}"`, ++ ].join(', '), ++ ) ++ console.log( ++ JSON.stringify({ ++ logType: 'docs_timing', ++ workerInstance, ++ docsRequest, ++ phase: 'headers_ready', ++ pathname: new URL(request.url).pathname, ++ ray: request.headers.get('cf-ray'), ++ status: response.status, ++ headersReadyMs: durationMs, ++ databaseUsed: timings.databaseUsed, ++ phases: timings.entries, ++ }), ++ ) ++ // This measures work through response creation, not streamed body completion. ++ const reader = response.body?.getReader() ++ let firstByte = true ++ const logBodyPhase = (phase: string) => ++ console.log( ++ JSON.stringify({ ++ logType: 'docs_timing', ++ workerInstance, ++ docsRequest, ++ phase, ++ pathname: new URL(request.url).pathname, ++ ray: request.headers.get('cf-ray'), ++ elapsedMs: performance.now() - timings.startedAt, ++ databaseUsed: timings.databaseUsed, ++ phases: timings.entries, ++ }), ++ ) ++ const body = reader ++ ? new ReadableStream({ ++ async pull(controller) { ++ try { ++ const result = await reader.read() ++ if (result.done) { ++ logBodyPhase('body_complete') ++ controller.close() ++ } else { ++ if (firstByte) { ++ firstByte = false ++ logBodyPhase('first_body_byte') ++ } ++ controller.enqueue(result.value) ++ } ++ } catch (error) { ++ logBodyPhase('body_error') ++ controller.error(error) ++ } ++ }, ++ async cancel(reason) { ++ logBodyPhase('body_cancelled') ++ await reader.cancel(reason) ++ }, ++ }) ++ : null ++ return new Response(body, { ++ status: response.status, ++ statusText: response.statusText, ++ headers, ++ }) ++ }) ++} +--- a/src/routes/__root.tsx ++++ b/src/routes/__root.tsx +@@ -275,7 +275,7 @@ + }, + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, + ], +- scripts: import.meta.env.PROD ++ scripts: import.meta.env.PROD && import.meta.env.MODE !== "staging" + ? [{ children: GOOGLE_ANALYTICS_BOOTSTRAP }] + : [], + } +@@ -462,6 +462,7 @@ + const lastTrackedPath = React.useRef(null) + + React.useEffect(() => { ++ if (import.meta.env.MODE === "staging") return + const stopTrackingHoverIntent = trackScarfExternalLinkHoverIntent() + document.addEventListener('click', trackScarfExternalLinkClick, true) + document.addEventListener('click', trackScarfDownloadClick, true) +@@ -485,7 +486,7 @@ + + lastTrackedPath.current = pagePath + trackPageView(pagePath) +- trackScarfPageView(window.location.pathname) ++ if (import.meta.env.MODE !== "staging") trackScarfPageView(window.location.pathname) + }, [pagePath]) + + return null +--- a/src/components/Scarf.tsx ++++ b/src/components/Scarf.tsx +@@ -24,7 +24,7 @@ + ) + + useEffect(() => { +- if (!visit || lastVisit.current === visit) return ++ if (import.meta.env.MODE === "staging" || !visit || lastVisit.current === visit) return + lastVisit.current = visit + setPixel({ + visit, +--- a/src/utils/analytics/scarf.ts ++++ b/src/utils/analytics/scarf.ts +@@ -4,7 +4,7 @@ + const CLIPBOARD_TEXT_LIMIT = 512 + + function sendScarfEvent(event: string, fields: Record) { +- if (import.meta.env.DEV || typeof window === 'undefined') { ++ if ((import.meta.env.DEV || import.meta.env.MODE === "staging") || typeof window === 'undefined') { + return + } + +@@ -67,7 +67,7 @@ + } + + export function trackScarfClipboardText(event: 'copy' | 'paste', text: string) { +- if (import.meta.env.DEV) return ++ if ((import.meta.env.DEV || import.meta.env.MODE === "staging")) return + + try { + navigator.sendBeacon( +--- a/src/utils/analytics/providers/google.ts ++++ b/src/utils/analytics/providers/google.ts +@@ -9,7 +9,7 @@ + + export const googleAnalyticsProvider: AnalyticsProvider = { + trackEvent(event, properties) { +- if (typeof window === 'undefined' || !window.gtag) { ++ if (import.meta.env.MODE === 'staging' || typeof window === 'undefined' || !window.gtag) { + return + } + +--- a/src/router.tsx ++++ b/src/router.tsx +@@ -13,7 +13,7 @@ + + Sentry.init({ + dsn: 'https://ac4bfc43ff4a892f8dc7053c4a50d92f@o4507236158537728.ingest.us.sentry.io/4507236163649536', +- enabled: import.meta.env.PROD, ++ enabled: import.meta.env.PROD && import.meta.env.MODE !== "staging", + sendDefaultPii: true, + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions +--- a/src/utils/sentry.server.ts ++++ b/src/utils/sentry.server.ts +@@ -9,7 +9,7 @@ + + Sentry.init({ + dsn: env.SENTRY_DSN ?? SENTRY_DSN, +- enabled: process.env.NODE_ENV === 'production' || Boolean(env.SENTRY_DSN), ++ enabled: import.meta.env.MODE !== 'staging' && (process.env.NODE_ENV === 'production' || Boolean(env.SENTRY_DSN)), + tracesSampleRate: 1.0, + environment: process.env.NODE_ENV ?? 'development', + }) diff --git a/staging/browser.json b/staging/browser.json new file mode 100644 index 000000000..73bf52b1d --- /dev/null +++ b/staging/browser.json @@ -0,0 +1,10 @@ +{ + "status": 200, + "url": "https://tanstack-perf-stage-site.thetanstack.workers.dev/query/latest/docs/framework/react/installation", + "title": "Installation | TanStack Query React Docs", + "errors": [], + "chatRequests": [], + "telemetryRequests": [], + "noindex": "noindex, nofollow", + "documentNavigations": 1 +} \ No newline at end of file diff --git a/staging/deployments.json b/staging/deployments.json new file mode 100644 index 000000000..bc187b960 --- /dev/null +++ b/staging/deployments.json @@ -0,0 +1,8 @@ +{ + "site": "5e0f8f26-8a8e-4400-a241-ca18b6211234", + "unified": "58e5df45-da03-4ecf-a0f1-09288c66310b", + "chat": "1d446108-ab86-4cf1-af08-b256fdddd2fa", + "og": "bd259cfb-981f-4dce-9f13-96776047c7ad", + "starter": "deffaebb-7dea-4828-b830-8d163475a354", + "namespace_ids_preserved": true +} diff --git a/staging/measurements.json b/staging/measurements.json new file mode 100644 index 000000000..55a002693 --- /dev/null +++ b/staging/measurements.json @@ -0,0 +1,882 @@ +[ + { + "kind": "unified", + "sample": 0, + "status": 200, + "ttfb_ms": 1414.161, + "after_connection_ms": 1329.9730000000002, + "total_ms": 1547.25, + "docs_request": 1, + "server_timing": "app_headers;dur=190.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559663f869398-DFW" + }, + { + "kind": "split", + "sample": 0, + "status": 200, + "ttfb_ms": 903.134, + "after_connection_ms": 822.5, + "total_ms": 1035.683, + "docs_request": 1, + "server_timing": "app_headers;dur=145.0, db_config;dur=0.0, content_edge_read;dur=289.0, start_handler;dur=145.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b5597188e2ca89-DFW" + }, + { + "kind": "split", + "sample": 1, + "status": 200, + "ttfb_ms": 1273.679, + "after_connection_ms": 1191.799, + "total_ms": 1404.199, + "docs_request": 1, + "server_timing": "app_headers;dur=207.0, db_config;dur=0.0, content_edge_read;dur=380.0, start_handler;dur=207.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55979ae8b6bd8-DFW" + }, + { + "kind": "unified", + "sample": 1, + "status": 200, + "ttfb_ms": 2323.8819999999996, + "after_connection_ms": 2218.2769999999996, + "total_ms": 2462.645, + "docs_request": 1, + "server_timing": "app_headers;dur=331.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559843d276b6a-DFW" + }, + { + "kind": "unified", + "sample": 2, + "status": 200, + "ttfb_ms": 1432.416, + "after_connection_ms": 1352.7, + "total_ms": 1563.783, + "docs_request": 1, + "server_timing": "app_headers;dur=218.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559953aba69d6-DFW" + }, + { + "kind": "split", + "sample": 2, + "status": 200, + "ttfb_ms": 741.694, + "after_connection_ms": 664.7449999999999, + "total_ms": 875.866, + "docs_request": 1, + "server_timing": "app_headers;dur=105.0, db_config;dur=0.0, content_edge_read;dur=210.0, start_handler;dur=105.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559a09be4f840-DFW" + }, + { + "kind": "split", + "sample": 3, + "status": 200, + "ttfb_ms": 740.7139999999999, + "after_connection_ms": 661.352, + "total_ms": 1232.69, + "docs_request": 1, + "server_timing": "app_headers;dur=111.0, db_config;dur=0.0, content_edge_read;dur=222.0, start_handler;dur=111.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559a7cbab378f-DFW" + }, + { + "kind": "unified", + "sample": 3, + "status": 200, + "ttfb_ms": 1902.9499999999998, + "after_connection_ms": 1819.901, + "total_ms": 2036.5069999999998, + "docs_request": 1, + "server_timing": "app_headers;dur=175.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559b14ee62fe4-DFW" + }, + { + "kind": "unified", + "sample": 4, + "status": 200, + "ttfb_ms": 1630.173, + "after_connection_ms": 1550.626, + "total_ms": 1760.29, + "docs_request": 1, + "server_timing": "app_headers;dur=183.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559bf9b762d57-DFW" + }, + { + "kind": "split", + "sample": 4, + "status": 200, + "ttfb_ms": 292.959, + "after_connection_ms": 212.548, + "total_ms": 424.03000000000003, + "docs_request": 2, + "server_timing": "app_headers;dur=6.0, db_config;dur=0.0, content_edge_read;dur=11.0, start_handler;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b559cc48113848-DFW" + }, + { + "kind": "split", + "sample": 5, + "status": 200, + "ttfb_ms": 387.59, + "after_connection_ms": 115.12899999999998, + "total_ms": 533.713, + "docs_request": 2, + "server_timing": "app_headers;dur=10.0, db_config;dur=0.0, content_edge_read;dur=15.0, start_handler;dur=10.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b559d1d9689a2f-DFW" + }, + { + "kind": "unified", + "sample": 5, + "status": 200, + "ttfb_ms": 2158.337, + "after_connection_ms": 2080.3509999999997, + "total_ms": 2288.8419999999996, + "docs_request": 1, + "server_timing": "app_headers;dur=312.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559d59cea461e-DFW" + }, + { + "kind": "unified", + "sample": 6, + "status": 200, + "ttfb_ms": 1345.664, + "after_connection_ms": 1265.6779999999999, + "total_ms": 1477.0700000000002, + "docs_request": 1, + "server_timing": "app_headers;dur=193.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559e5ae9888de-DFW" + }, + { + "kind": "split", + "sample": 6, + "status": 200, + "ttfb_ms": 791.3149999999999, + "after_connection_ms": 713.802, + "total_ms": 924.078, + "docs_request": 1, + "server_timing": "app_headers;dur=117.0, db_config;dur=0.0, content_edge_read;dur=233.0, start_handler;dur=117.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559f08c1867aa-DFW" + }, + { + "kind": "split", + "sample": 7, + "status": 200, + "ttfb_ms": 190.697, + "after_connection_ms": 112.638, + "total_ms": 321.49699999999996, + "docs_request": 2, + "server_timing": "app_headers;dur=10.0, db_config;dur=0.0, content_edge_read;dur=15.0, start_handler;dur=10.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b559f7fd5f323e-DFW" + }, + { + "kind": "unified", + "sample": 7, + "status": 200, + "ttfb_ms": 1977.943, + "after_connection_ms": 1897.511, + "total_ms": 2108.4030000000002, + "docs_request": 1, + "server_timing": "app_headers;dur=290.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b559fbbc436996-DFW" + }, + { + "kind": "unified", + "sample": 8, + "status": 200, + "ttfb_ms": 214.379, + "after_connection_ms": 131.43899999999996, + "total_ms": 346.711, + "docs_request": 2, + "server_timing": "app_headers;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a0a98b161f3-DFW" + }, + { + "kind": "split", + "sample": 8, + "status": 200, + "ttfb_ms": 176.815, + "after_connection_ms": 98.215, + "total_ms": 305.584, + "docs_request": 3, + "server_timing": "app_headers;dur=4.0, db_config;dur=0.0, content_edge_read;dur=8.0, start_handler;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a0e6f83f14a-DFW" + }, + { + "kind": "split", + "sample": 9, + "status": 200, + "ttfb_ms": 699.864, + "after_connection_ms": 619.369, + "total_ms": 840.474, + "docs_request": 1, + "server_timing": "app_headers;dur=120.0, db_config;dur=0.0, content_edge_read;dur=240.0, start_handler;dur=120.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55a11f9e1aee6-DFW" + }, + { + "kind": "unified", + "sample": 9, + "status": 200, + "ttfb_ms": 212.614, + "after_connection_ms": 129.494, + "total_ms": 344.50199999999995, + "docs_request": 2, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a18fa487fbc-DFW" + }, + { + "kind": "unified", + "sample": 10, + "status": 200, + "ttfb_ms": 1796.119, + "after_connection_ms": 1716.885, + "total_ms": 1928.352, + "docs_request": 1, + "server_timing": "app_headers;dur=266.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55a1ccc8bf098-DFW" + }, + { + "kind": "split", + "sample": 10, + "status": 200, + "ttfb_ms": 216.517, + "after_connection_ms": 137.921, + "total_ms": 346.202, + "docs_request": 3, + "server_timing": "app_headers;dur=4.0, db_config;dur=0.0, content_edge_read;dur=8.0, start_handler;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a2a79656e71-DFW" + }, + { + "kind": "split", + "sample": 11, + "status": 200, + "ttfb_ms": 1041.396, + "after_connection_ms": 961.77, + "total_ms": 1172.929, + "docs_request": 1, + "server_timing": "app_headers;dur=182.0, db_config;dur=0.0, content_edge_read;dur=363.0, start_handler;dur=182.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55a2e599d3ac2-DFW" + }, + { + "kind": "unified", + "sample": 11, + "status": 200, + "ttfb_ms": 1381.152, + "after_connection_ms": 1302.994, + "total_ms": 1514.326, + "docs_request": 1, + "server_timing": "app_headers;dur=208.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55a375cbce956-DFW" + }, + { + "kind": "unified", + "sample": 12, + "status": 200, + "ttfb_ms": 222.012, + "after_connection_ms": 143.35999999999999, + "total_ms": 354.51899999999995, + "docs_request": 2, + "server_timing": "app_headers;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a427d64c9d7-DFW" + }, + { + "kind": "split", + "sample": 12, + "status": 200, + "ttfb_ms": 176.637, + "after_connection_ms": 99.82499999999999, + "total_ms": 327.57599999999996, + "docs_request": 4, + "server_timing": "app_headers;dur=5.0, db_config;dur=0.0, content_edge_read;dur=9.0, start_handler;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55a4658393b9b-DFW" + }, + { + "kind": "split", + "sample": 13, + "status": 200, + "ttfb_ms": 195.636, + "after_connection_ms": 116.428, + "total_ms": 338.95799999999997, + "docs_request": 2, + "server_timing": "app_headers;dur=8.0, db_config;dur=0.0, content_edge_read;dur=12.0, start_handler;dur=8.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a4a18061bcb-DFW" + }, + { + "kind": "unified", + "sample": 13, + "status": 200, + "ttfb_ms": 228.516, + "after_connection_ms": 149.256, + "total_ms": 362.478, + "docs_request": 3, + "server_timing": "app_headers;dur=7.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a4ddcebe790-DFW" + }, + { + "kind": "unified", + "sample": 14, + "status": 200, + "ttfb_ms": 179.707, + "after_connection_ms": 99.33800000000001, + "total_ms": 308.017, + "docs_request": 3, + "server_timing": "app_headers;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a51c9adb421-DFW" + }, + { + "kind": "split", + "sample": 14, + "status": 200, + "ttfb_ms": 186.96099999999998, + "after_connection_ms": 111.99299999999998, + "total_ms": 318.061, + "docs_request": 3, + "server_timing": "app_headers;dur=3.0, db_config;dur=0.0, content_edge_read;dur=6.0, start_handler;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a555d5f4f01-DFW" + }, + { + "kind": "split", + "sample": 15, + "status": 200, + "ttfb_ms": 244.408, + "after_connection_ms": 162.489, + "total_ms": 376.47999999999996, + "docs_request": 2, + "server_timing": "app_headers;dur=12.0, db_config;dur=0.0, content_edge_read;dur=17.0, start_handler;dur=12.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a591f696c69-DFW" + }, + { + "kind": "unified", + "sample": 15, + "status": 200, + "ttfb_ms": 257.964, + "after_connection_ms": 180.92500000000004, + "total_ms": 389.964, + "docs_request": 2, + "server_timing": "app_headers;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a5d08c4e5c2-DFW" + }, + { + "kind": "unified", + "sample": 16, + "status": 200, + "ttfb_ms": 226.17999999999998, + "after_connection_ms": 145.951, + "total_ms": 355.38399999999996, + "docs_request": 3, + "server_timing": "app_headers;dur=7.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a613e82e5ad-DFW" + }, + { + "kind": "split", + "sample": 16, + "status": 200, + "ttfb_ms": 232.026, + "after_connection_ms": 154.18400000000003, + "total_ms": 361.73499999999996, + "docs_request": 2, + "server_timing": "app_headers;dur=10.0, db_config;dur=0.0, content_edge_read;dur=14.0, start_handler;dur=10.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a651f0266fb-DFW" + }, + { + "kind": "split", + "sample": 17, + "status": 200, + "ttfb_ms": 1186.298, + "after_connection_ms": 1108.605, + "total_ms": 1320.158, + "docs_request": 1, + "server_timing": "app_headers;dur=208.0, db_config;dur=0.0, content_edge_read;dur=404.0, start_handler;dur=208.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55a68fc46b789-DFW" + }, + { + "kind": "unified", + "sample": 17, + "status": 200, + "ttfb_ms": 223.454, + "after_connection_ms": 144.293, + "total_ms": 352.65999999999997, + "docs_request": 2, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a72dc70f09e-DFW" + }, + { + "kind": "unified", + "sample": 18, + "status": 200, + "ttfb_ms": 199.243, + "after_connection_ms": 120.424, + "total_ms": 328.61600000000004, + "docs_request": 2, + "server_timing": "app_headers;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a76be3569de-DFW" + }, + { + "kind": "split", + "sample": 18, + "status": 200, + "ttfb_ms": 781.7429999999999, + "after_connection_ms": 703.3979999999999, + "total_ms": 914.177, + "docs_request": 1, + "server_timing": "app_headers;dur=122.0, db_config;dur=0.0, content_edge_read;dur=243.0, start_handler;dur=122.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55a7a7b6bf0ae-DFW" + }, + { + "kind": "split", + "sample": 19, + "status": 200, + "ttfb_ms": 253.406, + "after_connection_ms": 174.82500000000002, + "total_ms": 385.43, + "docs_request": 2, + "server_timing": "app_headers;dur=15.0, db_config;dur=0.0, content_edge_read;dur=21.0, start_handler;dur=15.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a81df313588-DFW" + }, + { + "kind": "unified", + "sample": 19, + "status": 200, + "ttfb_ms": 306.179, + "after_connection_ms": 225.34799999999998, + "total_ms": 438.21700000000004, + "docs_request": 2, + "server_timing": "app_headers;dur=29.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a85fb7f6b8f-DFW" + }, + { + "kind": "unified", + "sample": 20, + "status": 200, + "ttfb_ms": 219.84799999999998, + "after_connection_ms": 140.50199999999998, + "total_ms": 348.921, + "docs_request": 3, + "server_timing": "app_headers;dur=7.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a8a6a5c6b5f-DFW" + }, + { + "kind": "split", + "sample": 20, + "status": 200, + "ttfb_ms": 256.79999999999995, + "after_connection_ms": 180.49499999999995, + "total_ms": 394.26, + "docs_request": 1, + "server_timing": "app_headers;dur=6.0, db_config;dur=0.0, content_edge_read;dur=12.0, start_handler;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55a8e3e4b5e98-DFW" + }, + { + "kind": "split", + "sample": 21, + "status": 200, + "ttfb_ms": 239.69099999999997, + "after_connection_ms": 124.68199999999999, + "total_ms": 374.98900000000003, + "docs_request": 2, + "server_timing": "app_headers;dur=8.0, db_config;dur=0.0, content_edge_read;dur=16.0, start_handler;dur=8.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a929d0d7330-DFW" + }, + { + "kind": "unified", + "sample": 21, + "status": 200, + "ttfb_ms": 208.52599999999998, + "after_connection_ms": 131.08899999999997, + "total_ms": 341.05, + "docs_request": 2, + "server_timing": "app_headers;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a965d263848-DFW" + }, + { + "kind": "unified", + "sample": 22, + "status": 200, + "ttfb_ms": 202.512, + "after_connection_ms": 124.36, + "total_ms": 332.825, + "docs_request": 2, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55a9a1f362cde-DFW" + }, + { + "kind": "split", + "sample": 22, + "status": 200, + "ttfb_ms": 197.902, + "after_connection_ms": 118.648, + "total_ms": 331.32800000000003, + "docs_request": 3, + "server_timing": "app_headers;dur=5.0, db_config;dur=0.0, content_edge_read;dur=10.0, start_handler;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55a9ddd9e67ae-DFW" + }, + { + "kind": "split", + "sample": 23, + "status": 200, + "ttfb_ms": 232.82500000000002, + "after_connection_ms": 153.633, + "total_ms": 364.13, + "docs_request": 3, + "server_timing": "app_headers;dur=6.0, db_config;dur=0.0, content_edge_read;dur=11.0, start_handler;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55aa19b796b3b-DFW" + }, + { + "kind": "unified", + "sample": 23, + "status": 200, + "ttfb_ms": 1351.414, + "after_connection_ms": 1273.859, + "total_ms": 1858.418, + "docs_request": 1, + "server_timing": "app_headers;dur=188.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55aa58a261db5-DFW" + }, + { + "kind": "unified", + "sample": 24, + "status": 200, + "ttfb_ms": 191.822, + "after_connection_ms": 116.589, + "total_ms": 335.501, + "docs_request": 3, + "server_timing": "app_headers;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55ab2ce842da4-DFW" + }, + { + "kind": "split", + "sample": 24, + "status": 200, + "ttfb_ms": 214.15, + "after_connection_ms": 135.859, + "total_ms": 345.777, + "docs_request": 4, + "server_timing": "app_headers;dur=3.0, db_config;dur=0.0, content_edge_read;dur=6.0, start_handler;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55ab69efe9b2f-DFW" + }, + { + "kind": "split", + "sample": 25, + "status": 200, + "ttfb_ms": 294.976, + "after_connection_ms": 207.50300000000001, + "total_ms": 425.73699999999997, + "docs_request": 4, + "server_timing": "app_headers;dur=7.0, db_config;dur=0.0, content_edge_read;dur=14.0, start_handler;dur=7.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55aba7d6d6c4f-DFW" + }, + { + "kind": "unified", + "sample": 25, + "status": 200, + "ttfb_ms": 305.093, + "after_connection_ms": 226.84799999999998, + "total_ms": 435.375, + "docs_request": 4, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55abeb95ceab2-DFW" + }, + { + "kind": "unified", + "sample": 26, + "status": 200, + "ttfb_ms": 203.02100000000002, + "after_connection_ms": 125.143, + "total_ms": 334.964, + "docs_request": 2, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55ac3180258c5-DFW" + }, + { + "kind": "split", + "sample": 26, + "status": 200, + "ttfb_ms": 202.048, + "after_connection_ms": 120.73700000000001, + "total_ms": 334.515, + "docs_request": 4, + "server_timing": "app_headers;dur=5.0, db_config;dur=0.0, content_edge_read;dur=9.0, start_handler;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55ac6eb0df475-DFW" + }, + { + "kind": "split", + "sample": 27, + "status": 200, + "ttfb_ms": 198.434, + "after_connection_ms": 113.114, + "total_ms": 329.584, + "docs_request": 2, + "server_timing": "app_headers;dur=11.0, db_config;dur=0.0, content_edge_read;dur=15.0, start_handler;dur=11.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55acaaca5f05a-DFW" + }, + { + "kind": "unified", + "sample": 27, + "status": 200, + "ttfb_ms": 197.512, + "after_connection_ms": 118.29999999999998, + "total_ms": 330.792, + "docs_request": 3, + "server_timing": "app_headers;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55ace6bfcf0ae-DFW" + }, + { + "kind": "unified", + "sample": 28, + "status": 200, + "ttfb_ms": 179.757, + "after_connection_ms": 102.733, + "total_ms": 311.295, + "docs_request": 5, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"5\"", + "ray": "a4b55ad21d7db1ee-DFW" + }, + { + "kind": "split", + "sample": 28, + "status": 200, + "ttfb_ms": 259.73, + "after_connection_ms": 178.32500000000002, + "total_ms": 390.091, + "docs_request": 2, + "server_timing": "app_headers;dur=3.0, db_config;dur=0.0, content_edge_read;dur=6.0, start_handler;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55ad5b856321a-DFW" + }, + { + "kind": "split", + "sample": 29, + "status": 200, + "ttfb_ms": 194.086, + "after_connection_ms": 116.15800000000002, + "total_ms": 325.709, + "docs_request": 3, + "server_timing": "app_headers;dur=4.0, db_config;dur=0.0, content_edge_read;dur=7.0, start_handler;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55ad9cef755cb-DFW" + }, + { + "kind": "unified", + "sample": 29, + "status": 200, + "ttfb_ms": 187.109, + "after_connection_ms": 107.251, + "total_ms": 317.141, + "docs_request": 6, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"6\"", + "ray": "a4b55add99bfe20a-DFW" + }, + { + "kind": "unified", + "sample": 30, + "status": 200, + "ttfb_ms": 183.29000000000002, + "after_connection_ms": 103.9, + "total_ms": 325.26800000000003, + "docs_request": 4, + "server_timing": "app_headers;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55ae1395b20bf-DFW" + }, + { + "kind": "split", + "sample": 30, + "status": 200, + "ttfb_ms": 196.128, + "after_connection_ms": 112.8, + "total_ms": 326.64, + "docs_request": 5, + "server_timing": "app_headers;dur=4.0, db_config;dur=0.0, content_edge_read;dur=7.0, start_handler;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"5\"", + "ray": "a4b55ae4f8a114e1-DFW" + }, + { + "kind": "split", + "sample": 31, + "status": 200, + "ttfb_ms": 164.981, + "after_connection_ms": 88.401, + "total_ms": 293.603, + "docs_request": 6, + "server_timing": "app_headers;dur=3.0, db_config;dur=0.0, content_edge_read;dur=6.0, start_handler;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"6\"", + "ray": "a4b55ae8a87d0864-DFW" + }, + { + "kind": "unified", + "sample": 31, + "status": 200, + "ttfb_ms": 183.298, + "after_connection_ms": 102.92999999999999, + "total_ms": 314.649, + "docs_request": 3, + "server_timing": "app_headers;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55aec2c19f7c2-DFW" + }, + { + "kind": "unified", + "sample": 32, + "status": 200, + "ttfb_ms": 315.711, + "after_connection_ms": 238.58700000000002, + "total_ms": 445.536, + "docs_request": 4, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55aefc950f0b4-DFW" + }, + { + "kind": "split", + "sample": 32, + "status": 200, + "ttfb_ms": 191.089, + "after_connection_ms": 111.613, + "total_ms": 322.262, + "docs_request": 4, + "server_timing": "app_headers;dur=3.0, db_config;dur=0.0, content_edge_read;dur=6.0, start_handler;dur=3.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55af44c17dc2d-DFW" + }, + { + "kind": "split", + "sample": 33, + "status": 200, + "ttfb_ms": 159.065, + "after_connection_ms": 82.93800000000002, + "total_ms": 292.171, + "docs_request": 4, + "server_timing": "app_headers;dur=5.0, db_config;dur=0.0, content_edge_read;dur=10.0, start_handler;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55af7ece869d3-DFW" + }, + { + "kind": "unified", + "sample": 33, + "status": 200, + "ttfb_ms": 198.52700000000002, + "after_connection_ms": 121.42100000000002, + "total_ms": 331.366, + "docs_request": 3, + "server_timing": "app_headers;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55afb7882ca89-DFW" + }, + { + "kind": "unified", + "sample": 34, + "status": 200, + "ttfb_ms": 274.69599999999997, + "after_connection_ms": 193.89999999999998, + "total_ms": 405.262, + "docs_request": 2, + "server_timing": "app_headers;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55aff3db74740-DFW" + }, + { + "kind": "split", + "sample": 34, + "status": 200, + "ttfb_ms": 197.46800000000002, + "after_connection_ms": 116.732, + "total_ms": 330.19100000000003, + "docs_request": 3, + "server_timing": "app_headers;dur=5.0, db_config;dur=0.0, content_edge_read;dur=10.0, start_handler;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"3\"", + "ray": "a4b55b038d6cf07a-DFW" + }, + { + "kind": "split", + "sample": 35, + "status": 200, + "ttfb_ms": 1241.247, + "after_connection_ms": 1159.785, + "total_ms": 1374.748, + "docs_request": 1, + "server_timing": "app_headers;dur=223.0, db_config;dur=0.0, content_edge_read;dur=446.0, start_handler;dur=223.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55b0749194638-DFW" + }, + { + "kind": "unified", + "sample": 35, + "status": 200, + "ttfb_ms": 1422.091, + "after_connection_ms": 1345.8719999999998, + "total_ms": 1551.348, + "docs_request": 1, + "server_timing": "app_headers;dur=186.0, db_used;desc=\"false\", docs_request;desc=\"1\"", + "ray": "a4b55b118b492871-DFW" + }, + { + "kind": "unified", + "sample": 36, + "status": 200, + "ttfb_ms": 213.075, + "after_connection_ms": 136.13799999999998, + "total_ms": 345.137, + "docs_request": 7, + "server_timing": "app_headers;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"7\"", + "ray": "a4b55b1ce951416c-DFW" + }, + { + "kind": "split", + "sample": 36, + "status": 200, + "ttfb_ms": 194.904, + "after_connection_ms": 113.79299999999999, + "total_ms": 330.887, + "docs_request": 4, + "server_timing": "app_headers;dur=5.0, db_config;dur=0.0, content_edge_read;dur=10.0, start_handler;dur=5.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55b20bf3f244e-DFW" + }, + { + "kind": "split", + "sample": 37, + "status": 200, + "ttfb_ms": 228.519, + "after_connection_ms": 151.449, + "total_ms": 358.254, + "docs_request": 5, + "server_timing": "app_headers;dur=6.0, db_config;dur=0.0, content_edge_read;dur=11.0, start_handler;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"5\"", + "ray": "a4b55b247c7d1002-DFW" + }, + { + "kind": "unified", + "sample": 37, + "status": 200, + "ttfb_ms": 358.65999999999997, + "after_connection_ms": 280.416, + "total_ms": 485.712, + "docs_request": 4, + "server_timing": "app_headers;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55b2858c2504f-DFW" + }, + { + "kind": "unified", + "sample": 38, + "status": 200, + "ttfb_ms": 254.60500000000002, + "after_connection_ms": 167.32300000000004, + "total_ms": 385.343, + "docs_request": 4, + "server_timing": "app_headers;dur=6.0, db_used;desc=\"false\", docs_request;desc=\"4\"", + "ray": "a4b55b2d2eed6c43-DFW" + }, + { + "kind": "split", + "sample": 38, + "status": 200, + "ttfb_ms": 277.13599999999997, + "after_connection_ms": 198.20399999999998, + "total_ms": 407.311, + "docs_request": 2, + "server_timing": "app_headers;dur=16.0, db_config;dur=0.0, content_edge_read;dur=32.0, start_handler;dur=16.0, db_used;desc=\"false\", docs_request;desc=\"2\"", + "ray": "a4b55b312ecd4752-DFW" + }, + { + "kind": "split", + "sample": 39, + "status": 200, + "ttfb_ms": 294.296, + "after_connection_ms": 176.072, + "total_ms": 429.82, + "docs_request": 5, + "server_timing": "app_headers;dur=60.0, db_config;dur=0.0, content_edge_read;dur=63.0, start_handler;dur=60.0, db_used;desc=\"false\", docs_request;desc=\"5\"", + "ray": "a4b55b3599f98cb8-DFW" + }, + { + "kind": "unified", + "sample": 39, + "status": 200, + "ttfb_ms": 186.774, + "after_connection_ms": 107.66099999999999, + "total_ms": 321.637, + "docs_request": 8, + "server_timing": "app_headers;dur=4.0, db_used;desc=\"false\", docs_request;desc=\"8\"", + "ray": "a4b55b39bfe1b94c-DFW" + } +] diff --git a/staging/state-after-rollback.json b/staging/state-after-rollback.json new file mode 100644 index 000000000..e4365e7c5 --- /dev/null +++ b/staging/state-after-rollback.json @@ -0,0 +1,44 @@ +[ + { + "id": "4651e38f155643828ca053285f505d5d", + "name": "tanstack-perf-stage-chat_StreamObject", + "script": "tanstack-perf-stage-chat", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "4bc05d1c72ab4aa3ad405020b0fe65be", + "name": "tanstack-perf-stage-chat_Conversation", + "script": "tanstack-perf-stage-chat", + "class": "Conversation", + "use_sqlite": true + }, + { + "id": "f03cfa387d274727b998314a531346b4", + "name": "tanstack-perf-stage-chat_WorkspaceSync", + "script": "tanstack-perf-stage-chat", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "bc6bf2ef2daa48cca297edff98348661", + "name": "tanstack-perf-stage-baseline_StreamObject", + "script": "tanstack-perf-stage-baseline", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "3e7aa9a74e2349029e8612ae77fd3d82", + "name": "tanstack-perf-stage-baseline_WorkspaceSync", + "script": "tanstack-perf-stage-baseline", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "83ac8130974a4d3e91e11485a073bb5c", + "name": "tanstack-perf-stage-baseline_Conversation", + "script": "tanstack-perf-stage-baseline", + "class": "Conversation", + "use_sqlite": true + } +] diff --git a/staging/state-before-rollback.json b/staging/state-before-rollback.json new file mode 100644 index 000000000..e4365e7c5 --- /dev/null +++ b/staging/state-before-rollback.json @@ -0,0 +1,44 @@ +[ + { + "id": "4651e38f155643828ca053285f505d5d", + "name": "tanstack-perf-stage-chat_StreamObject", + "script": "tanstack-perf-stage-chat", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "4bc05d1c72ab4aa3ad405020b0fe65be", + "name": "tanstack-perf-stage-chat_Conversation", + "script": "tanstack-perf-stage-chat", + "class": "Conversation", + "use_sqlite": true + }, + { + "id": "f03cfa387d274727b998314a531346b4", + "name": "tanstack-perf-stage-chat_WorkspaceSync", + "script": "tanstack-perf-stage-chat", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "bc6bf2ef2daa48cca297edff98348661", + "name": "tanstack-perf-stage-baseline_StreamObject", + "script": "tanstack-perf-stage-baseline", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "3e7aa9a74e2349029e8612ae77fd3d82", + "name": "tanstack-perf-stage-baseline_WorkspaceSync", + "script": "tanstack-perf-stage-baseline", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "83ac8130974a4d3e91e11485a073bb5c", + "name": "tanstack-perf-stage-baseline_Conversation", + "script": "tanstack-perf-stage-baseline", + "class": "Conversation", + "use_sqlite": true + } +] diff --git a/staging/state-final.json b/staging/state-final.json new file mode 100644 index 000000000..e4365e7c5 --- /dev/null +++ b/staging/state-final.json @@ -0,0 +1,44 @@ +[ + { + "id": "4651e38f155643828ca053285f505d5d", + "name": "tanstack-perf-stage-chat_StreamObject", + "script": "tanstack-perf-stage-chat", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "4bc05d1c72ab4aa3ad405020b0fe65be", + "name": "tanstack-perf-stage-chat_Conversation", + "script": "tanstack-perf-stage-chat", + "class": "Conversation", + "use_sqlite": true + }, + { + "id": "f03cfa387d274727b998314a531346b4", + "name": "tanstack-perf-stage-chat_WorkspaceSync", + "script": "tanstack-perf-stage-chat", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "bc6bf2ef2daa48cca297edff98348661", + "name": "tanstack-perf-stage-baseline_StreamObject", + "script": "tanstack-perf-stage-baseline", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "3e7aa9a74e2349029e8612ae77fd3d82", + "name": "tanstack-perf-stage-baseline_WorkspaceSync", + "script": "tanstack-perf-stage-baseline", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "83ac8130974a4d3e91e11485a073bb5c", + "name": "tanstack-perf-stage-baseline_Conversation", + "script": "tanstack-perf-stage-baseline", + "class": "Conversation", + "use_sqlite": true + } +] diff --git a/tests/builder-openrouter.test.ts b/tests/builder-openrouter.test.ts index c0939359f..a6df14c3e 100644 --- a/tests/builder-openrouter.test.ts +++ b/tests/builder-openrouter.test.ts @@ -9,7 +9,7 @@ import { startBuilderOpenRouterLogin, isOpenRouterCallback, } from '../src/utils/builder-openrouter-login.client' -import { openRouterCallbackResponse } from '../src/routes/api/chat/openrouter/callback' +import { openRouterCallbackResponse } from '../src/chat/server/routes/openrouter/callback' import { createBuilderAiByokConnection } from '../src/utils/builder-ai-api-key-storage.client' import { streamBuilderAiResponse } from '../src/utils/builder-ai' import { createExampleWorkspace } from '../src/utils/example-workspace' diff --git a/tests/builder-project-quarantine.test.ts b/tests/builder-project-quarantine.test.ts index 867ddb939..3b0d15c56 100644 --- a/tests/builder-project-quarantine.test.ts +++ b/tests/builder-project-quarantine.test.ts @@ -3,7 +3,7 @@ import test from 'node:test' import { BuilderProjectQuarantineCleanupError, quarantineBuilderProjectSnapshotForAdmin, -} from '../src/routes/api/chat/project-snapshots.$hash.quarantine' +} from '../src/chat/server/routes/project-snapshots.$hash.quarantine' const hash = 'a'.repeat(64) const actorId = '00000000-0000-4000-8000-000000000001' diff --git a/tests/chat-entrypoints.test.ts b/tests/chat-entrypoints.test.ts new file mode 100644 index 000000000..e25b7bae4 --- /dev/null +++ b/tests/chat-entrypoints.test.ts @@ -0,0 +1,51 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import test from 'node:test' +import ts from 'typescript' + +function publicMethods(path: string, name: string) { + const source = ts.createSourceFile( + path, + readFileSync(path, 'utf8'), + ts.ScriptTarget.Latest, + true, + ) + const declaration = source.statements.find( + (node): node is ts.ClassDeclaration => + ts.isClassDeclaration(node) && node.name?.text === name, + ) + assert.ok(declaration, `${name} must exist`) + return declaration.members + .filter((node): node is ts.MethodDeclaration => + ts.isMethodDeclaration(node), + ) + .filter( + (node) => + !node.modifiers?.some( + (modifier) => + modifier.kind === ts.SyntaxKind.PrivateKeyword || + modifier.kind === ts.SyntaxKind.ProtectedKeyword || + modifier.kind === ts.SyntaxKind.StaticKeyword, + ), + ) + .map((node) => node.name.getText(source)) + .sort() +} + +// A missing method here would silently remove a deployed RPC endpoint. +for (const [name, implementation] of [ + ['Conversation', 'src/chat/server/conversation.ts'], + ['WorkspaceSync', 'src/chat/server/workspace-sync-object.ts'], + [ + 'StreamObject', + 'node_modules/@durable-streams/server-cloudflare/dist/index.d.ts', + ], + ['TanChatWorkflow', 'src/chat/server/workflow-driver.ts'], +]) { + test(`lazy ${name} preserves every public RPC and lifecycle method`, () => { + assert.deepEqual( + publicMethods('src/chat/server/entries.ts', name), + publicMethods(implementation, name), + ) + }) +} diff --git a/tests/docs-html-headers.test.ts b/tests/docs-html-headers.test.ts new file mode 100644 index 000000000..85cb5ebe9 --- /dev/null +++ b/tests/docs-html-headers.test.ts @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { finalizeDocsHtmlHeaders } from '../src/utils/docs-html-headers.server' + +const url = 'https://tanstack.com/router/latest/docs/routing/routing-concepts' +test('HTML headers override loader headers without enabling page caching', () => { + const response = finalizeDocsHtmlHeaders( + new Request(url, { headers: { Accept: 'text/html' } }), + new Response('docs', { + headers: { + 'X-Docs-Html': '1', + 'Cache-Control': 'public, max-age=0', + 'Cache-Tag': 'docs-config:all', + }, + }), + ) + assert.equal(response.headers.has('X-Docs-Html'), false) + assert.equal(response.headers.get('Cache-Control'), 'private, no-store') + assert.equal(response.headers.get('Cloudflare-CDN-Cache-Control'), 'no-store') + assert.match( + response.headers.get('Cache-Tag') ?? '', + /docs:router:branch:main/, + ) + assert.match( + response.headers.get('Cache-Tag') ?? '', + /docs-config:tanstack\/router:main/, + ) +}) +test('fallback and authenticated HTML stay uncacheable even with conflicting loader headers', () => { + for (const marker of ['1', 'no-store']) { + const response = finalizeDocsHtmlHeaders( + new Request(url, { + headers: { Cookie: 'session_token=x', Accept: 'text/html' }, + }), + new Response('docs', { + headers: { + 'X-Docs-Html': marker, + 'Cache-Control': 'public, max-age=3600', + }, + }), + ) + assert.equal(response.headers.get('Cache-Control'), 'private, no-store') + assert.equal( + response.headers.get('Cloudflare-CDN-Cache-Control'), + 'no-store', + ) + } +}) diff --git a/tests/docs-manifest-cache.test.ts b/tests/docs-manifest-cache.test.ts new file mode 100644 index 000000000..503c6f89a --- /dev/null +++ b/tests/docs-manifest-cache.test.ts @@ -0,0 +1,92 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { dehydrate, hydrate, QueryClient } from '@tanstack/react-query' +import { docsPathManifestQueryOptions } from '../src/queries/docsManifest' +import { resolveDocsRoutePath } from '../src/utils/docs' + +const request = { + repo: 'tanstack/router', + branch: 'main', + docsRoot: 'docs/router', +} + +test('docs navigation reuses the manifest hydrated by the server', async () => { + const serverClient = new QueryClient() + serverClient.setQueryData(docsPathManifestQueryOptions(request).queryKey, { + paths: ['routing/routing-concepts', 'routing/route-trees'], + redirects: {}, + }) + const browserClient = new QueryClient() + hydrate(browserClient, dehydrate(serverClient)) + + try { + // These resolve entirely from hydrated data, without a server request. + for (const docsPath of [ + 'routing/routing-concepts', + 'routing/route-trees', + ]) { + assert.deepEqual( + await resolveDocsRoutePath({ + ...request, + queryClient: browserClient, + defaultDocs: 'overview', + frameworks: ['react', 'solid'], + docsPath, + }), + { type: 'render', docsPath }, + ) + } + + assert.deepEqual( + await resolveDocsRoutePath({ + ...request, + queryClient: browserClient, + defaultDocs: 'overview', + frameworks: ['react', 'solid'], + docsPath: 'framework/react/routing/route-trees', + }), + { type: 'redirect', docsPath: 'routing/route-trees' }, + ) + } finally { + serverClient.clear() + browserClient.clear() + } +}) + +test('cached manifests stay separate for each repository, branch, and docs root', async () => { + const queryClient = new QueryClient() + const requests = [ + request, + { ...request, repo: 'tanstack/query' }, + { ...request, branch: 'v0' }, + { ...request, docsRoot: 'docs/start' }, + ] + + try { + for (const [index, data] of requests.entries()) { + queryClient.setQueryData(docsPathManifestQueryOptions(data).queryKey, { + paths: [`page-${index}`], + redirects: {}, + }) + } + + for (const [index, data] of requests.entries()) { + const opts = { + ...data, + queryClient, + defaultDocs: 'overview', + frameworks: ['react'], + } + assert.deepEqual( + await resolveDocsRoutePath({ ...opts, docsPath: `page-${index}` }), + { type: 'render', docsPath: `page-${index}` }, + ) + assert.deepEqual( + await resolveDocsRoutePath({ ...opts, docsPath: 'missing' }), + { type: 'not-found' }, + ) + } + } finally { + queryClient.clear() + } +}) diff --git a/tests/github-content-cache.test.ts b/tests/github-content-cache.test.ts index f2a85b455..a5c6590f9 100644 --- a/tests/github-content-cache.test.ts +++ b/tests/github-content-cache.test.ts @@ -425,6 +425,63 @@ async function testStaleArtifactRefreshesInWaitUntil() { assert.deepEqual(freshResult, { paths: ['new'] }) } +async function testExpiredContentRefreshesWithoutBlocking() { + resetGitHubContentCacheForTest() + const mockR2 = createMockR2Bucket() + const pending: Array> = [] + let finish: ((value: string) => void) | undefined + const refresh = new Promise((resolve) => { + finish = resolve + }) + await runWithHostRuntimeEnv( + { GITHUB_CONTENT_CACHE: mockR2.bucket }, + async () => { + const opts = { repo, gitRef, path: 'docs/expired.md' } + await getCachedGitHubTextFile({ ...opts, origin: async () => 'old' }) + for (const object of mockR2.objects.values()) { + if (object.customMetadata) + object.customMetadata.staleAt = new Date( + Date.now() - 1000, + ).toISOString() + } + const value = await runWithHostRuntimeContext( + { + waitUntil: (task: Promise) => { + pending.push(task) + }, + }, + () => getCachedGitHubTextFile({ ...opts, origin: () => refresh }), + ) + assert.equal(value, 'old') + assert.equal(pending.length, 1) + finish?.('new') + await Promise.all(pending) + assert.equal( + await getCachedGitHubTextFile({ + ...opts, + origin: async () => { + throw new Error('should be fresh') + }, + }), + 'new', + ) + await markGitHubContentStale({ repo, gitRef }) + const forced = await runWithHostRuntimeContext( + { + waitUntil: () => { + assert.fail('forced invalidation must block') + }, + }, + () => + getCachedGitHubTextFile({ ...opts, origin: async () => 'forced' }), + ) + assert.equal(forced, 'forced') + }, + ) +} + +await testExpiredContentRefreshesWithoutBlocking() + await testMissStoresContent() await testFreshHitSkipsOrigin() await testWorkerEnvUsesBlobStorage() diff --git a/tests/request-timing.test.ts b/tests/request-timing.test.ts new file mode 100644 index 000000000..2bb1b7f8d --- /dev/null +++ b/tests/request-timing.test.ts @@ -0,0 +1,51 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { + markRequestDatabaseUse, + timeRequestWork, + withDocsRequestTimings, +} from '../src/utils/request-timing.server' + +const request = new Request('https://tanstack.com/router/latest/docs/overview') +test('timings are request-local and preserve streamed responses', async () => { + const [used, unused] = await Promise.all([ + withDocsRequestTimings(request, async () => { + await timeRequestWork('content_r2_read', async () => { + markRequestDatabaseUse() + }) + return new Response('first', { status: 201 }) + }), + withDocsRequestTimings(request, async () => new Response('second')), + ]) + assert.match(used.headers.get('Server-Timing') ?? '', /content_r2_read;dur=/) + assert.match(used.headers.get('Server-Timing') ?? '', /db_used;desc="true"/) + assert.match( + unused.headers.get('Server-Timing') ?? '', + /db_used;desc="false"/, + ) + assert.equal(used.status, 201) + assert.equal(await used.text(), 'first') + assert.equal(await unused.text(), 'second') +}) +test('failed work is timed without swallowing the error', async () => { + const response = await withDocsRequestTimings(request, async () => { + await assert.rejects( + timeRequestWork('content_origin', async () => { + throw new Error('origin failed') + }), + /origin failed/, + ) + return new Response('fallback') + }) + assert.match( + response.headers.get('Server-Timing') ?? '', + /content_origin;dur=/, + ) +}) +test('non-docs requests have no profiling headers', async () => { + const response = await withDocsRequestTimings( + new Request('https://tanstack.com/account'), + async () => new Response('account'), + ) + assert.equal(response.headers.has('Server-Timing'), false) +}) diff --git a/tests/worker-boundaries.test.ts b/tests/worker-boundaries.test.ts new file mode 100644 index 000000000..20c2a14e6 --- /dev/null +++ b/tests/worker-boundaries.test.ts @@ -0,0 +1,172 @@ +import { z } from 'zod' +import assert from 'node:assert/strict' +import { readFileSync, readdirSync } from 'node:fs' +import path from 'node:path' +import test from 'node:test' +import ts from 'typescript' +import { defineHttpRoute } from '../src/server/runtime/http-route.server' +import { chatHttpRoutes } from '../src/chat/server/http/routes' +import { starterHttpRoutes } from '../src/application-starter/api/http/routes' +import { runWithHostRuntimeEnv } from '../src/server/runtime/host.server' +import { forwardWorkerRequest } from '../src/server/runtime/worker-service.server' + +function routeFiles(directory: string): string[] { + return readdirSync(directory, { withFileTypes: true }) + .flatMap((entry) => + entry.isDirectory() + ? routeFiles(path.join(directory, entry.name)) + : [path.join(directory, entry.name)], + ) + .filter((file) => file.endsWith('.ts')) +} + +for (const group of [ + { + name: 'chat', + directories: ['src/routes/api/chat', 'src/routes/auth/mcp'], + routes: chatHttpRoutes, + }, + { + name: 'starter', + directories: ['src/routes/api/application-starter'], + routes: starterHttpRoutes, + }, +]) { + test(`every public ${group.name} route has the same methods in its Worker`, () => { + const files = group.directories.flatMap(routeFiles) + const routes = group.routes + assert.equal(routes.length, files.length) + for (const file of files) { + const source = ts.createSourceFile( + file, + readFileSync(file, 'utf8'), + ts.ScriptTarget.Latest, + true, + ) + let routePath = '' + const methods: string[] = [] + function visit(node: ts.Node) { + if ( + ts.isCallExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === 'createFileRoute' + ) { + const argument = node.arguments[0] + assert.ok(ts.isStringLiteral(argument)) + routePath = argument.text + } + if ( + ts.isPropertyAssignment(node) && + node.name.getText(source) === 'handlers' + ) { + assert.ok(ts.isObjectLiteralExpression(node.initializer)) + methods.push( + ...node.initializer.properties.map( + (property) => property.name?.getText(source) ?? '', + ), + ) + } + ts.forEachChild(node, visit) + } + visit(source) + const route = routes.find((route) => route.path === routePath) + assert.ok(route, routePath) + const url = + 'https://tanstack.com' + + routePath.replace(/\$([^/]*)/g, (_, name) => + name ? 'example' : 'account/example', + ) + for (const method of methods) { + const match = route.match(new Request(url, { method })) + assert.ok(match?.handler, `${method} ${routePath}`) + } + } + }) +} + +test('specific chat routes win over parameters and the MCP splat', () => { + for (const pathname of [ + '/api/chat/mcp/contract', + '/api/chat/bots/bulk', + '/api/chat/bots/move', + ]) { + const request = new Request('https://tanstack.com' + pathname) + assert.equal( + chatHttpRoutes.find((route) => route.match(request))?.path, + pathname, + ) + } +}) + +test('Worker routing decodes parameters, accepts trailing slashes and preserves splats', () => { + const route = defineHttpRoute( + '/api/chat/projects/$id', + z.object({ id: z.string() }), + { + GET: () => new Response(), + }, + ) + const match = route.match( + new Request('https://tanstack.com/api/chat/projects/a%20b/'), + ) + assert.equal(match?.context.params.id, 'a b') + const splat = defineHttpRoute( + '/api/chat/mcp/$', + z.object({ _splat: z.string() }), + { + GET: () => new Response(), + }, + ) + assert.equal( + splat.match(new Request('https://tanstack.com/api/chat/mcp/account/one')) + ?.context.params._splat, + 'account/one', + ) + assert.ok( + route.match( + new Request('https://tanstack.com/api/chat/projects/one', { + method: 'HEAD', + }), + )?.handler, + ) + assert.equal( + route.match( + new Request('https://tanstack.com/api/chat/projects/one', { + method: 'DELETE', + }), + )?.handler, + undefined, + ) +}) + +test('service forwarding preserves the original request and streamed response', async () => { + const request = new Request('https://tanstack.com/api/chat/draft?scope=one', { + method: 'POST', + headers: { + Cookie: 'session=example', + Origin: 'https://tanstack.com', + 'Content-Type': 'application/json', + }, + body: '{"text":"hello"}', + }) + const response = new Response('event: update\ndata: hello\n\n', { + headers: { 'Content-Type': 'text/event-stream', 'Set-Cookie': 'example=1' }, + }) + const actual = await runWithHostRuntimeEnv( + { + CHAT: { + async fetch(forwarded: Request) { + assert.equal(forwarded, request) + assert.equal(forwarded.headers.get('Cookie'), 'session=example') + assert.equal(forwarded.headers.get('Origin'), 'https://tanstack.com') + assert.equal(await forwarded.text(), '{"text":"hello"}') + return response + }, + }, + }, + () => forwardWorkerRequest('CHAT', request), + ) + assert.equal(actual, response) + assert.equal(actual.headers.get('Set-Cookie'), 'example=1') + assert.match(await actual.text(), /event: update/) +}) diff --git a/tests/worker-deployment.test.ts b/tests/worker-deployment.test.ts new file mode 100644 index 000000000..4ad113314 --- /dev/null +++ b/tests/worker-deployment.test.ts @@ -0,0 +1,90 @@ +import assert from 'node:assert/strict' +import { + mkdtempSync, + mkdirSync, + writeFileSync, + readFileSync, + rmSync, + existsSync, +} from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { spawnSync } from 'node:child_process' +import test from 'node:test' + +function fixture(productionBinding = false) { + const directory = mkdtempSync(path.join(tmpdir(), 'staging-deployment-')) + const bin = path.join(directory, 'bin') + mkdirSync(bin) + for (const kind of ['og', 'starter', 'chat', 'server']) { + const name = `tanstack-perf-stage-${kind === 'server' ? 'site' : kind}` + mkdirSync(path.join(directory, 'dist', kind), { recursive: true }) + writeFileSync( + path.join(directory, 'dist', kind, 'wrangler.json'), + JSON.stringify({ + name, + services: + productionBinding && kind === 'server' + ? [{ service: 'tanstack-com' }] + : [], + }), + ) + } + writeFileSync( + path.join(bin, 'pnpm'), + `#!/usr/bin/env node +const fs = require('node:fs') +fs.appendFileSync('calls.jsonl', JSON.stringify({args:process.argv.slice(2),override:process.env.WRANGLER_CI_OVERRIDE_NAME,tag:process.env.WRANGLER_CI_MATCH_TAG})+'\\n') +`, + { mode: 0o755 }, + ) + const result = spawnSync( + process.execPath, + [path.resolve('scripts/deploy-worker-staging.mjs')], + { + cwd: directory, + env: { + ...process.env, + PATH: bin + path.delimiter + process.env.PATH, + WRANGLER_CI_OVERRIDE_NAME: 'tanstack-com', + WRANGLER_CI_MATCH_TAG: 'production-tag', + }, + encoding: 'utf8', + }, + ) + const log = path.join(directory, 'calls.jsonl') + const calls = existsSync(log) + ? readFileSync(log, 'utf8') + .trim() + .split('\n') + .map((line) => JSON.parse(line)) + : [] + rmSync(directory, { recursive: true, force: true }) + return { result, calls } +} + +test('Cloudflare Builds overrides cannot redirect any staging upload', () => { + const { result, calls } = fixture() + assert.equal(result.status, 0, result.stderr) + assert.equal(calls.length, 4) + assert.deepEqual( + calls.map((call) => call.args[call.args.indexOf('--name') + 1]), + [ + 'tanstack-perf-stage-og', + 'tanstack-perf-stage-starter', + 'tanstack-perf-stage-chat', + 'tanstack-perf-stage-site', + ], + ) + for (const call of calls) { + assert.equal(call.override, undefined) + assert.equal(call.tag, undefined) + } +}) + +test('a production binding aborts the entire fleet before the first upload', () => { + const { result, calls } = fixture(true) + assert.notEqual(result.status, 0) + assert.match(result.stderr, /Non-staging service binding/) + assert.deepEqual(calls, []) +}) diff --git a/vite.config.ts b/vite.config.ts index 1a9a09c98..660ecfff8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,6 +22,8 @@ import { } from './src/utils/local-repo-path.server' import { startLocalMcpEgress } from './scripts/local-mcp-egress' import { localBuilderAi } from './scripts/local-builder-ai-vite' +import { chatIsolation } from './scripts/chat-isolation-vite' +import { workerIsolation } from './scripts/worker-isolation-vite' const isDev = process.env.NODE_ENV !== 'production' const shouldUseRedact = process.env.DISABLE_REDACT !== 'true' @@ -31,7 +33,7 @@ const shouldUseSentryPlugin = Boolean(process.env.SENTRY_AUTH_TOKEN) const shouldBuildSourcemaps = shouldUseSentryPlugin || process.env.BUILD_SOURCEMAPS === 'true' -const SITE_URL = 'https://tanstack.com' +const SITE_URL = process.env.STAGING_SITE_URL || 'https://tanstack.com' const localDocsDevToken = isDev ? randomUUID() : '' function localDocsDevFiles(): PluginOption { @@ -214,6 +216,8 @@ function chatBuildId() { } export default defineConfig(async ({ command, mode }) => { + if (command === 'build' && mode !== 'staging') + throw new Error('This experimental branch only builds with --mode staging') const localEnv = { ...loadEnv(mode, __dirname, ''), ...process.env } const useRemoteAi = Boolean(localEnv.CLOUDFLARE_API_TOKEN) const egress = command === 'serve' ? await startLocalMcpEgress() : undefined @@ -225,7 +229,11 @@ export default defineConfig(async ({ command, mode }) => { __TANSTACK_ENABLE_SERVER_BUILDER_GENERATION__: JSON.stringify(true), __TANSTACK_ENABLE_IMAGE_TRANSFORMATIONS__: JSON.stringify(!isDev), __TANSTACK_LOCAL_DOCS_TOKEN__: JSON.stringify(localDocsDevToken), - __TANSTACK_SITE_URL__: JSON.stringify(SITE_URL), + __TANSTACK_SITE_URL__: JSON.stringify( + mode === 'staging' + ? 'https://tanstack-perf-stage-site.thetanstack.workers.dev' + : SITE_URL, + ), }, resolve: { alias: { @@ -364,8 +372,56 @@ export default defineConfig(async ({ command, mode }) => { ] : []), cloudflare({ + configPath: + mode === 'staging' ? '.staging/server.json' : 'wrangler.jsonc', viteEnvironment: { name: 'ssr' }, remoteBindings: useRemoteAi, + auxiliaryWorkers: [ + { + configPath: + mode === 'staging' + ? '.staging/og.json' + : 'workers/og/wrangler.jsonc', + viteEnvironment: { name: 'og' }, + }, + { + configPath: + mode === 'staging' + ? '.staging/starter.json' + : 'workers/starter/wrangler.jsonc', + viteEnvironment: { name: 'starter' }, + config: (config) => { + if (command !== 'serve') return + config.hyperdrive = [] + config.vars = { ...config.vars, APP_MODE: 'development' } + }, + }, + { + configPath: + mode === 'staging' + ? '.staging/chat.json' + : 'workers/chat/wrangler.jsonc', + viteEnvironment: { name: 'chat' }, + config: (config) => { + if (command !== 'serve') return + config.hyperdrive = [] + if (!useRemoteAi) delete config.ai + config.vars = { + ...config.vars, + ...egress?.vars, + APP_MODE: 'development', + } + for (const name of [ + 'GUM_DEV_EXECUTION', + 'UNLIMITED_USAGE_EMAILS', + 'USAGE_RATES_JSON', + ]) { + const value = localEnv[name] + if (typeof value === 'string') config.vars[name] = value + } + }, + }, + ], config: (config) => { if (command !== 'serve') return config.hyperdrive = [] @@ -445,6 +501,8 @@ export default defineConfig(async ({ command, mode }) => { }, }), viteReact(), + chatIsolation(), + workerIsolation(), ...(shouldUseSentryPlugin ? [ diff --git a/workers/chat/wrangler.jsonc b/workers/chat/wrangler.jsonc new file mode 100644 index 000000000..0b8c9e236 --- /dev/null +++ b/workers/chat/wrangler.jsonc @@ -0,0 +1,83 @@ +{ + "account_id": "8da95258a9c70b54c3e2b374a0079106", + "compatibility_date": "2026-06-19", + "compatibility_flags": ["nodejs_compat"], + "observability": { + "enabled": true, + }, + "limits": { + "cpu_ms": 300000, + }, + "name": "tanstack-chat", + "main": "../../src/workers/chat.ts", + "workers_dev": false, + "preview_urls": false, + "vars": { + "APP_MODE": "production", + "MCP_EGRESS_MODE": "cloudflare-public", + "KODY_ORIGIN": "https://kody.codes", + "INCLUDED_MODEL": "@cf/moonshotai/kimi-k2.6", + }, + "durable_objects": { + "bindings": [ + { + "name": "STREAMS", + "class_name": "StreamObject", + }, + { + "name": "WORKSPACE_SYNC", + "class_name": "WorkspaceSync", + }, + { + "name": "CONVERSATIONS", + "class_name": "Conversation", + }, + ], + }, + "workflows": [ + { + "name": "tanstack-chat-workflows", + "binding": "WORKFLOW_RUNS", + "class_name": "TanChatWorkflow", + }, + ], + "triggers": { + "crons": ["* * * * *"], + }, + "ai": { + "binding": "AI", + }, + "r2_buckets": [ + { + "binding": "BUILDER_PROJECTS", + "bucket_name": "tanstack-notebook-projects", + }, + { + "binding": "FILES", + "bucket_name": "tanstack-notebook-projects", + }, + ], + "hyperdrive": [ + { + "binding": "HYPERDRIVE", + "id": "6ec1513b87bb47e9897e5502727b0388", + }, + ], + "secrets": { + "required": ["SESSION_SECRET", "ENCRYPTION_KEY", "DATABASE_URL"], + }, + "exports": { + "StreamObject": { + "type": "durable-object", + "storage": "sqlite", + }, + "WorkspaceSync": { + "type": "durable-object", + "storage": "sqlite", + }, + "Conversation": { + "type": "durable-object", + "storage": "sqlite", + }, + }, +} diff --git a/workers/og/wrangler.jsonc b/workers/og/wrangler.jsonc new file mode 100644 index 000000000..c364e78b5 --- /dev/null +++ b/workers/og/wrangler.jsonc @@ -0,0 +1,21 @@ +{ + "account_id": "8da95258a9c70b54c3e2b374a0079106", + "compatibility_date": "2026-06-19", + "compatibility_flags": ["nodejs_compat"], + "observability": { + "enabled": true, + }, + "limits": { + "cpu_ms": 300000, + }, + "name": "tanstack-og", + "main": "../../src/workers/og.ts", + "workers_dev": false, + "preview_urls": false, + "services": [ + { + "binding": "ASSETS", + "service": "tanstack-com", + }, + ], +} diff --git a/workers/starter/wrangler.jsonc b/workers/starter/wrangler.jsonc new file mode 100644 index 000000000..d7cf255ce --- /dev/null +++ b/workers/starter/wrangler.jsonc @@ -0,0 +1,32 @@ +{ + "account_id": "8da95258a9c70b54c3e2b374a0079106", + "compatibility_date": "2026-06-19", + "compatibility_flags": ["nodejs_compat"], + "observability": { + "enabled": true, + }, + "limits": { + "cpu_ms": 300000, + }, + "workers_dev": false, + "preview_urls": false, + "hyperdrive": [ + { + "binding": "HYPERDRIVE", + "id": "6ec1513b87bb47e9897e5502727b0388", + }, + ], + "name": "tanstack-starter", + "main": "../../src/workers/starter.ts", + "vars": { + "APP_MODE": "production", + }, + "secrets": { + "required": [ + "SESSION_SECRET", + "ENCRYPTION_KEY", + "GITHUB_AUTH_TOKEN", + "DATABASE_URL", + ], + }, +} diff --git a/wrangler.jsonc b/wrangler.jsonc index e6072a2a5..179cb13c1 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -20,45 +20,15 @@ "limits": { "cpu_ms": 300000, }, - "durable_objects": { - "bindings": [ - { - "name": "STREAMS", - "class_name": "StreamObject", - "script_name": "tanstack-chat", - }, - { - "name": "WORKSPACE_SYNC", - "class_name": "WorkspaceSync", - "script_name": "tanstack-chat", - }, - { - "name": "CONVERSATIONS", - "class_name": "Conversation", - "script_name": "tanstack-chat", - }, - ], - }, - "exports": { - "StreamObject": { "type": "durable-object", "storage": "sqlite" }, - "WorkspaceSync": { "type": "durable-object", "storage": "sqlite" }, - "Conversation": { "type": "durable-object", "storage": "sqlite" }, - }, - "workflows": [ - { - "name": "tanstack-chat-workflows", - "binding": "WORKFLOW_RUNS", - "class_name": "TanChatWorkflow", - "script_name": "tanstack-chat", - }, - ], "triggers": { "crons": ["* * * * *", "0 9 * * *", "0 */6 * * *"], }, "assets": { "binding": "ASSETS", }, - "ai": { "binding": "AI" }, + "ai": { + "binding": "AI", + }, "r2_buckets": [ { "binding": "GITHUB_CONTENT_CACHE", @@ -83,4 +53,18 @@ "id": "6ec1513b87bb47e9897e5502727b0388", }, ], + "services": [ + { + "binding": "OG", + "service": "tanstack-og", + }, + { + "binding": "CHAT", + "service": "tanstack-chat", + }, + { + "binding": "STARTER", + "service": "tanstack-starter", + }, + ], } From ba3f6e933e3f4704007e53b62fd819166adb7ecc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:21:26 +0000 Subject: [PATCH 02/11] ci: apply automated fixes --- staging/README.md | 8 ++++---- staging/browser.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/staging/README.md b/staging/README.md index 703babf1d..455a1d395 100644 --- a/staging/README.md +++ b/staging/README.md @@ -37,11 +37,11 @@ alongside timing results. All 80 requests returned 200 from DFW. Median TTFB in milliseconds: -| Requests | Unified | Split | -| --- | ---: | ---: | +| Requests | Unified | Split | +| ----------------------------- | -----------------: | ---------------: | | First observed in an instance | 1,531 (12 samples) | 791 (11 samples) | -| Reused instance | 213 (28 samples) | 202 (29 samples) | -| All requests | 227 | 236 | +| Reused instance | 213 (28 samples) | 202 (29 samples) | +| All requests | 227 | 236 | First observed requests were 48% faster in this sample. Overall p95 fell from 1,978 ms to 1,186 ms. The overall median did not improve. This supports a cold diff --git a/staging/browser.json b/staging/browser.json index 73bf52b1d..88d413168 100644 --- a/staging/browser.json +++ b/staging/browser.json @@ -7,4 +7,4 @@ "telemetryRequests": [], "noindex": "noindex, nofollow", "documentNavigations": 1 -} \ No newline at end of file +} From d137285e0d029c61e5a9d815e2131551b6977346 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 16 Oct 2026 13:25:17 -0600 Subject: [PATCH 03/11] Verify staging Builds and authenticated flows against isolated Neon --- scripts/check-worker-staging-auth-browser.cjs | 80 +++++++++ scripts/check-worker-staging-auth.mjs | 160 +++++++++++++++++ scripts/prepare-worker-staging.mjs | 7 +- scripts/provision-worker-staging-db.mjs | 67 +++++++ scripts/seed-worker-staging.ts | 57 ++++++ src/db/client.ts | 6 + staging/README.md | 42 ++++- staging/auth-browser.json | 8 + staging/auth-flows.json | 164 ++++++++++++++++++ wrangler.jsonc | 2 +- 10 files changed, 587 insertions(+), 6 deletions(-) create mode 100644 scripts/check-worker-staging-auth-browser.cjs create mode 100644 scripts/check-worker-staging-auth.mjs create mode 100644 scripts/provision-worker-staging-db.mjs create mode 100644 scripts/seed-worker-staging.ts create mode 100644 staging/auth-browser.json create mode 100644 staging/auth-flows.json diff --git a/scripts/check-worker-staging-auth-browser.cjs b/scripts/check-worker-staging-auth-browser.cjs new file mode 100644 index 000000000..f626e0546 --- /dev/null +++ b/scripts/check-worker-staging-auth-browser.cjs @@ -0,0 +1,80 @@ +const fs = require('node:fs') +const { chromium } = require('playwright-core') +const assert = require('node:assert/strict') +const origin = 'https://tanstack-perf-stage-site.thetanstack.workers.dev' +const sessions = JSON.parse(fs.readFileSync('.staging/sessions.json', 'utf8')) +;(async () => { + const browser = await chromium.launch({ + executablePath: + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + headless: true, + }) + try { + const context = await browser.newContext({ + viewport: { width: 1440, height: 1000 }, + }) + await context.addCookies([ + { + name: 'session_token', + value: encodeURIComponent(sessions.users[0].token), + url: origin, + httpOnly: true, + secure: true, + sameSite: 'Lax', + }, + ]) + const page = await context.newPage(), + errors = [], + forbiddenRequests = [] + await page.route('**/*', async (route) => { + const u = new URL(route.request().url()) + if ( + ['tanstack.com', 'chat.tanstack.com', 'kody.codes'].includes(u.hostname) + ) { + forbiddenRequests.push(u.origin + u.pathname) + await route.abort() + return + } + await route.continue() + }) + page.on('pageerror', (e) => errors.push(e.message)) + const response = await page.goto(origin + '/account', { + waitUntil: 'networkidle', + }) + await page + .getByRole('heading', { name: 'My Account', exact: true }) + .waitFor() + assert.equal(response.status(), 200) + const values = await page + .locator('input') + .evaluateAll((inputs) => inputs.map((input) => input.value)) + assert.ok(values.includes('staging-alice@example.invalid')) + await page.screenshot({ path: '/tmp/tanstack-staging-account.png' }) + await page.getByRole('button', { name: 'Logout', exact: true }).click() + await page.waitForURL('**/login') + const signedOut = await context.request.get( + origin + '/api/chat/account/preferences', + ) + assert.equal(signedOut.status(), 401) + assert.deepEqual(errors, []) + assert.deepEqual(forbiddenRequests, []) + const result = { + accountStatus: response.status(), + signedInUser: 'Staging Alice', + signedOutStatus: signedOut.status(), + url: page.url(), + errors, + forbiddenRequests, + } + fs.writeFileSync( + 'staging/auth-browser.json', + JSON.stringify(result, null, 2) + '\n', + ) + console.log(result) + } finally { + await browser.close() + } +})().catch((e) => { + console.error(e) + process.exit(1) +}) diff --git a/scripts/check-worker-staging-auth.mjs b/scripts/check-worker-staging-auth.mjs new file mode 100644 index 000000000..4fc7b277b --- /dev/null +++ b/scripts/check-worker-staging-auth.mjs @@ -0,0 +1,160 @@ +import fs from 'node:fs' +import assert from 'node:assert/strict' +const origin = 'https://tanstack-perf-stage-site.thetanstack.workers.dev' +const sessions = JSON.parse(fs.readFileSync('.staging/sessions.json', 'utf8')) +const [alice, bob] = sessions.users +const checks = [] +async function request( + label, + path, + token, + method = 'GET', + body, + source = origin, + expected = 200, +) { + if (!path.startsWith('/api/chat/')) + throw new Error('Unexpected staging test route') + const response = await fetch(origin + path, { + method, + redirect: 'manual', + headers: { + ...(token + ? { Cookie: 'session_token=' + encodeURIComponent(token) } + : {}), + ...(method !== 'GET' + ? { 'Content-Type': 'application/json', Origin: source } + : {}), + }, + ...(body !== undefined ? { body: JSON.stringify(body) } : {}), + }) + const data = await response.json() + checks.push({ label, status: response.status, expected }) + console.log({ + label, + status: response.status, + expected, + ...(response.status !== expected ? { response: data } : {}), + }) + assert.equal(response.status, expected, label) + return data +} +try { + await request( + 'anonymous rejected', + '/api/chat/account/preferences', + undefined, + 'GET', + undefined, + origin, + 401, + ) + const first = await request( + 'Alice signed-in read', + '/api/chat/account/preferences', + alice.token, + ) + const saved = await request( + 'Alice preference write', + '/api/chat/account/preferences', + alice.token, + 'POST', + { revision: first.revision, timezone: 'America/Denver' }, + ) + assert.equal(saved.timezone, 'America/Denver') + const second = await request( + 'Bob account isolation', + '/api/chat/account/preferences', + bob.token, + ) + assert.equal(second.timezone, null) + await request( + 'cross-origin mutation rejected', + '/api/chat/account/preferences', + alice.token, + 'POST', + { revision: saved.revision, timezone: 'UTC' }, + 'https://example.invalid', + 403, + ) + await request( + 'expired session rejected', + '/api/chat/account/preferences', + sessions.expired, + 'GET', + undefined, + origin, + 401, + ) + await request( + 'revoked session version rejected', + '/api/chat/account/preferences', + sessions.revoked, + 'GET', + undefined, + origin, + 401, + ) + await request( + 'nonexistent user rejected', + '/api/chat/account/preferences', + sessions.missing, + 'GET', + undefined, + origin, + 401, + ) + await request( + 'forged cookie rejected', + '/api/chat/account/preferences', + alice.token.slice(0, -4) + 'AAAA', + 'GET', + undefined, + origin, + 401, + ) + const workspaceA = await request( + 'Alice creates personal workspace', + '/api/chat/workspace', + alice.token, + 'POST', + {}, + ) + const workspaceB = await request( + 'Bob creates separate workspace', + '/api/chat/workspace', + bob.token, + 'POST', + {}, + ) + assert.notDeepEqual(workspaceA, workspaceB) + await request( + 'Alice reads staging Durable Object snapshot', + '/api/chat/workspace-sync/snapshot?workspaceId=' + + encodeURIComponent('personal:' + alice.id), + alice.token, + ) + await request( + 'Bob cannot read Alice workspace', + '/api/chat/workspace-sync/snapshot?workspaceId=' + + encodeURIComponent('personal:' + alice.id), + bob.token, + 'GET', + undefined, + origin, + 403, + ) + fs.writeFileSync( + 'staging/auth-flows.json', + JSON.stringify( + { checks, workspaceA, workspaceB, syntheticUsersOnly: true }, + null, + 2, + ) + '\n', + ) +} finally { + fs.writeFileSync( + '/tmp/tanstack-staging-auth-checks.json', + JSON.stringify(checks, null, 2) + '\n', + ) +} diff --git a/scripts/prepare-worker-staging.mjs b/scripts/prepare-worker-staging.mjs index 6b8b9ab14..1a8bf1099 100644 --- a/scripts/prepare-worker-staging.mjs +++ b/scripts/prepare-worker-staging.mjs @@ -27,7 +27,12 @@ for (const [kind, source] of Object.entries({ config.triggers = { crons: [] } config.hyperdrive = [] delete config.ai - config.secrets = { required: ['SESSION_SECRET', 'ENCRYPTION_KEY'] } + config.secrets = { + required: + kind === 'og' + ? ['SESSION_SECRET', 'ENCRYPTION_KEY'] + : ['SESSION_SECRET', 'ENCRYPTION_KEY', 'DATABASE_URL'], + } config.vars = { APP_MODE: 'production', SENTRY_DSN: '' } config.r2_buckets = (config.r2_buckets ?? []).map((binding) => ({ binding: binding.binding, diff --git a/scripts/provision-worker-staging-db.mjs b/scripts/provision-worker-staging-db.mjs new file mode 100644 index 000000000..e5544c41c --- /dev/null +++ b/scripts/provision-worker-staging-db.mjs @@ -0,0 +1,67 @@ +import fs from 'node:fs' +import path from 'node:path' +import postgres from 'postgres' +import { generateDrizzleJson, generateMigration } from 'drizzle-kit/api' +import { drizzle } from 'drizzle-orm/postgres-js' +import { migrate } from 'drizzle-orm/postgres-js/migrator' +const c = JSON.parse(fs.readFileSync('.staging/credentials.json', 'utf8')) +const direct = new URL(c.DIRECT_DATABASE_URL) +if (direct.hostname !== 'ep-blue-rice-b4fz0smd.c-6.us-east-2.aws.neon.tech') + throw new Error('Not staging') +const admin = postgres(direct.toString(), { max: 1 }) +const existing = + await admin`SELECT 1 FROM pg_database WHERE datname='worker_staging_auth'` +if (!existing.length) await admin.unsafe('CREATE DATABASE worker_staging_auth') +await admin.end() +direct.pathname = '/worker_staging_auth' +const pooled = new URL(c.DATABASE_URL) +pooled.pathname = direct.pathname +const folder = '.staging/baseline-migrations' +fs.mkdirSync(path.join(folder, 'meta'), { recursive: true }) +const snapshot = JSON.parse( + fs.readFileSync('drizzle/migrations/meta/0000_snapshot.json', 'utf8'), +) +const statements = await generateMigration(generateDrizzleJson({}), snapshot) +fs.writeFileSync( + path.join(folder, '0000_staging_baseline.sql'), + statements.join('\n--> statement-breakpoint\n'), +) +const journal = JSON.parse( + fs.readFileSync('drizzle/migrations/meta/_journal.json', 'utf8'), +) +journal.entries[0].tag = '0000_staging_baseline' +fs.writeFileSync( + path.join(folder, 'meta/_journal.json'), + JSON.stringify(journal), +) +for (const entry of journal.entries.slice(1)) + fs.copyFileSync( + `drizzle/migrations/${entry.tag}.sql`, + path.join(folder, `${entry.tag}.sql`), + ) +const sql = postgres(direct.toString(), { max: 1 }) +try { + await migrate(drizzle(sql), { migrationsFolder: folder }) + const tables = + await sql`select count(*)::int as count from information_schema.tables where table_schema='public'` + const triggers = + await sql`select count(*)::int as count from information_schema.triggers where trigger_schema='public'` + fs.writeFileSync( + '.staging/credentials.json', + JSON.stringify({ + ...c, + DATABASE_URL: pooled.toString(), + DIRECT_DATABASE_URL: direct.toString(), + }), + { mode: 0o600 }, + ) + console.log({ + database: direct.pathname, + tables: tables[0].count, + triggers: triggers[0].count, + baseline: '0000_snapshot.json', + migrations: journal.entries.length, + }) +} finally { + await sql.end() +} diff --git a/scripts/seed-worker-staging.ts b/scripts/seed-worker-staging.ts new file mode 100644 index 000000000..115403e2c --- /dev/null +++ b/scripts/seed-worker-staging.ts @@ -0,0 +1,57 @@ +import fs from 'node:fs' +import postgres from 'postgres' +import { randomUUID } from 'node:crypto' +import { SessionService } from '../src/auth/session.server' +const c = JSON.parse(fs.readFileSync('.staging/credentials.json', 'utf8')) +if ( + new URL(c.DIRECT_DATABASE_URL).hostname !== + 'ep-blue-rice-b4fz0smd.c-6.us-east-2.aws.neon.tech' +) + throw new Error('Not staging') +const sql = postgres(c.DIRECT_DATABASE_URL, { max: 1 }) +const service = new SessionService(c.SESSION_SECRET, true) +const users = [] +try { + for (const name of ['Alice', 'Bob']) { + const candidateId = randomUUID() + const [user] = + await sql`insert into users (id,email,name) values (${candidateId},${'staging-' + name.toLowerCase() + '@example.invalid'},${'Staging ' + name}) ON CONFLICT (email) DO UPDATE SET name=EXCLUDED.name RETURNING id` + const id = user.id + await sql`insert into chat_access (user_id) values (${id}) ON CONFLICT (user_id) DO NOTHING` + users.push({ + id, + name, + token: await service.signCookie({ + userId: id, + expiresAt: Date.now() + 86400000, + version: 0, + }), + }) + } + const expired = await service.signCookie({ + userId: users[0].id, + expiresAt: Date.now() - 1000, + version: 0, + }) + const revoked = await service.signCookie({ + userId: users[0].id, + expiresAt: Date.now() + 86400000, + version: 1, + }) + const missing = await service.signCookie({ + userId: randomUUID(), + expiresAt: Date.now() + 86400000, + version: 0, + }) + fs.writeFileSync( + '.staging/sessions.json', + JSON.stringify({ users, expired, revoked, missing }), + { mode: 0o600 }, + ) + console.log({ + seededUsers: users.map(({ id, name }) => ({ id, name })), + productionDataCopied: false, + }) +} finally { + await sql.end() +} diff --git a/src/db/client.ts b/src/db/client.ts index 12e0b1e51..52659e703 100644 --- a/src/db/client.ts +++ b/src/db/client.ts @@ -99,6 +99,12 @@ export async function runWithDatabaseContext( if (!connectionString) { return fn() } + if ( + import.meta.env.MODE === 'staging' && + new URL(connectionString).hostname !== + 'ep-blue-rice-b4fz0smd-pooler.c-6.us-east-2.aws.neon.tech' + ) + throw new Error('Staging can only connect to its isolated database') const context: DatabaseContext = { connectionString, active: 1 } return databaseStorage.run(context, async () => { diff --git a/staging/README.md b/staging/README.md index 455a1d395..cf984c08a 100644 --- a/staging/README.md +++ b/staging/README.md @@ -5,7 +5,8 @@ This branch is a staging experiment. Production deployment commands are disabled The split site is `https://tanstack-perf-stage-site.thetanstack.workers.dev`. The unified comparison is `https://tanstack-perf-stage-baseline.thetanstack.workers.dev`. -Both use only `tanstack-perf-stage-*` resources. No production secrets, database, +Both use only `tanstack-perf-stage-*` Workers and buckets. The signed-in checks +use a separate Neon project. No production secrets, production database, Hyperdrive, AI binding, custom domain, cron, or namespace transfer is configured. Session and encryption keys were generated for staging. Signed-in chat and project persistence are outside this public docs comparison. @@ -17,9 +18,10 @@ uploading anything. It removes `WRANGLER_CI_OVERRIDE_NAME` and explicitly. The installed Wrangler overrides even `--name` when that CI name variable is present. -The remote deployment is exercised with those CI variables set to staging -values. The production-name override case is tested offline. This reproduces -the relevant environment behavior, it is not a connected Cloudflare Builds job. +The initial remote deployment was exercised with those CI variables set to staging +values. The production-name override case is tested offline. That initial test reproduced +the relevant environment behavior. The connected Cloudflare Builds validation is +recorded below. `python3 scripts/measure-worker-staging.py` sends 80 alternating requests using fresh curl connections. Both sites share a staging-only GitHub content cache. @@ -67,3 +69,35 @@ lint. Four unit and one desktop test were skipped. The unified comparison can be rebuilt from the pinned main commit with `node scripts/build-worker-staging-baseline.mjs`. Its staging-only changes are recorded in `baseline.patch`. + +## Cloudflare Builds and signed-in checks + +The staging Worker is connected to the separate private repository +`TanStack/tanstack-com-worker-staging`. Its main branch runs `pnpm build:staging` +and `pnpm deploy:staging`. Preview builds are disabled. The first actual +Cloudflare Builds run, `89710348-86be-41a7-b1b4-76fc8174489e`, completed build and +deployment successfully. The production repository and its build settings were +not changed. + +The isolated Neon project is `tanstack-perf-stage` (`spring-dew-83008577`), and +its database is `worker_staging_auth`. No production data was copied. The +recorded `0000_snapshot.json` supplies the historical baseline, followed by all +remaining migrations. This creates 103 tables and 35 SQL triggers. Both existing +builder and chat schema verification commands pass. A schema-only push was +insufficient because it omitted the SQL triggers, so it is not the final setup. + +Staging credentials and synthetic sessions live only in the ignored `.staging` +directory. Runtime keys are installed only on staging Workers. The staging +server also rejects a database connection with any other hostname. + +Live checks pass for authenticated preferences reads and writes, two-user data +isolation, cross-origin rejection, expired cookies, mismatched session versions, +nonexistent users, forged signatures, separate personal workspaces, a real +WorkspaceSync Durable Object snapshot, and rejection of another user's snapshot. +The signed-in browser opens My Account and logs out, after which protected API +requests return 401. No browser request reached the production site or Kody. + +These tests use synthetic users and valid sessions signed by the existing +SessionService with staging-only keys. They do not test GitHub/Google OAuth +consent and callbacks, model inference, or Kody integrations. No production +OAuth or model credentials were copied. diff --git a/staging/auth-browser.json b/staging/auth-browser.json new file mode 100644 index 000000000..2bccbeedc --- /dev/null +++ b/staging/auth-browser.json @@ -0,0 +1,8 @@ +{ + "accountStatus": 200, + "signedInUser": "Staging Alice", + "signedOutStatus": 401, + "url": "https://tanstack-perf-stage-site.thetanstack.workers.dev/login", + "errors": [], + "forbiddenRequests": [] +} diff --git a/staging/auth-flows.json b/staging/auth-flows.json new file mode 100644 index 000000000..7b93c958a --- /dev/null +++ b/staging/auth-flows.json @@ -0,0 +1,164 @@ +{ + "checks": [ + { + "label": "anonymous rejected", + "status": 401, + "expected": 401 + }, + { + "label": "Alice signed-in read", + "status": 200, + "expected": 200 + }, + { + "label": "Alice preference write", + "status": 200, + "expected": 200 + }, + { + "label": "Bob account isolation", + "status": 200, + "expected": 200 + }, + { + "label": "cross-origin mutation rejected", + "status": 403, + "expected": 403 + }, + { + "label": "expired session rejected", + "status": 401, + "expected": 401 + }, + { + "label": "revoked session version rejected", + "status": 401, + "expected": 401 + }, + { + "label": "nonexistent user rejected", + "status": 401, + "expected": 401 + }, + { + "label": "forged cookie rejected", + "status": 401, + "expected": 401 + }, + { + "label": "Alice creates personal workspace", + "status": 200, + "expected": 200 + }, + { + "label": "Bob creates separate workspace", + "status": 200, + "expected": 200 + }, + { + "label": "Alice reads staging Durable Object snapshot", + "status": 200, + "expected": 200 + }, + { + "label": "Bob cannot read Alice workspace", + "status": 403, + "expected": 403 + } + ], + "workspaceA": { + "workspace": { + "id": "personal:51ab9cbf-8b23-4fd1-88eb-efed6f6f087d", + "ownerId": "51ab9cbf-8b23-4fd1-88eb-efed6f6f087d", + "name": "Personal", + "policy": { + "allowedProviders": [ + "included", + "openai", + "anthropic", + "gemini", + "groq", + "grok", + "openrouter", + "vercel", + "cloudflare", + "cf_gateway", + "compatible" + ], + "allowedModels": [], + "dailyTurns": 30, + "allowJev": true, + "allowChatModels": true, + "allowKody": true, + "allowMcp": true + }, + "createdAt": "2026-10-16T19:23:18.895Z" + }, + "bots": [ + { + "id": "assistant:51ab9cbf-8b23-4fd1-88eb-efed6f6f087d", + "workspaceId": "personal:51ab9cbf-8b23-4fd1-88eb-efed6f6f087d", + "parentId": null, + "name": "TanChat", + "purpose": "Your everyday assistant.", + "version": 0, + "avatar": null, + "deletionBatch": null, + "updatedAt": "2026-10-16T19:23:18.895Z", + "archivedAt": null, + "deletedAt": null, + "createdAt": "2026-10-16T19:23:18.895Z" + } + ], + "assistantId": "assistant:51ab9cbf-8b23-4fd1-88eb-efed6f6f087d", + "conversationId": "chat:assistant:51ab9cbf-8b23-4fd1-88eb-efed6f6f087d:51ab9cbf-8b23-4fd1-88eb-efed6f6f087d" + }, + "workspaceB": { + "workspace": { + "id": "personal:5795b054-0e52-4f71-9ce3-8b9df83eea34", + "ownerId": "5795b054-0e52-4f71-9ce3-8b9df83eea34", + "name": "Personal", + "policy": { + "allowedProviders": [ + "included", + "openai", + "anthropic", + "gemini", + "groq", + "grok", + "openrouter", + "vercel", + "cloudflare", + "cf_gateway", + "compatible" + ], + "allowedModels": [], + "dailyTurns": 30, + "allowJev": true, + "allowChatModels": true, + "allowKody": true, + "allowMcp": true + }, + "createdAt": "2026-10-16T19:23:20.399Z" + }, + "bots": [ + { + "id": "assistant:5795b054-0e52-4f71-9ce3-8b9df83eea34", + "workspaceId": "personal:5795b054-0e52-4f71-9ce3-8b9df83eea34", + "parentId": null, + "name": "TanChat", + "purpose": "Your everyday assistant.", + "version": 0, + "avatar": null, + "deletionBatch": null, + "updatedAt": "2026-10-16T19:23:20.399Z", + "archivedAt": null, + "deletedAt": null, + "createdAt": "2026-10-16T19:23:20.399Z" + } + ], + "assistantId": "assistant:5795b054-0e52-4f71-9ce3-8b9df83eea34", + "conversationId": "chat:assistant:5795b054-0e52-4f71-9ce3-8b9df83eea34:5795b054-0e52-4f71-9ce3-8b9df83eea34" + }, + "syntheticUsersOnly": true +} diff --git a/wrangler.jsonc b/wrangler.jsonc index 179cb13c1..87b3414a1 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -1,6 +1,6 @@ { "$schema": "node_modules/wrangler/config-schema.json", - "name": "tanstack-com", + "name": "tanstack-perf-stage-site", "account_id": "8da95258a9c70b54c3e2b374a0079106", "main": "src/server.ts", "compatibility_date": "2026-06-19", From 67a9211fd6b2d76a152d70d8c842d7e73b37e580 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 16 Oct 2026 13:26:09 -0600 Subject: [PATCH 04/11] Record actual CI destinations and keep staging checks in ESM --- ...browser.cjs => check-worker-staging-auth-browser.mjs} | 6 +++--- scripts/deploy-worker-staging.mjs | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) rename scripts/{check-worker-staging-auth-browser.cjs => check-worker-staging-auth-browser.mjs} (95%) diff --git a/scripts/check-worker-staging-auth-browser.cjs b/scripts/check-worker-staging-auth-browser.mjs similarity index 95% rename from scripts/check-worker-staging-auth-browser.cjs rename to scripts/check-worker-staging-auth-browser.mjs index f626e0546..a96e2881c 100644 --- a/scripts/check-worker-staging-auth-browser.cjs +++ b/scripts/check-worker-staging-auth-browser.mjs @@ -1,6 +1,6 @@ -const fs = require('node:fs') -const { chromium } = require('playwright-core') -const assert = require('node:assert/strict') +import fs from 'node:fs' +import { chromium } from 'playwright-core' +import assert from 'node:assert/strict' const origin = 'https://tanstack-perf-stage-site.thetanstack.workers.dev' const sessions = JSON.parse(fs.readFileSync('.staging/sessions.json', 'utf8')) ;(async () => { diff --git a/scripts/deploy-worker-staging.mjs b/scripts/deploy-worker-staging.mjs index 74beec49d..df6186882 100644 --- a/scripts/deploy-worker-staging.mjs +++ b/scripts/deploy-worker-staging.mjs @@ -41,6 +41,15 @@ for (const { config, expectedName } of configurations) { throw new Error('Namespace transfers are disabled in staging deploys') } +console.log( + JSON.stringify({ + target: 'staging-only', + ciWorker: process.env.WRANGLER_CI_OVERRIDE_NAME ?? null, + ciTagPresent: Boolean(process.env.WRANGLER_CI_MATCH_TAG), + destinations: [...names], + }), +) + // These variables are scoped to one connected Cloudflare Builds Worker. // Passing them to a different Worker overrides even an explicit --name. const env = { ...process.env } From 8ec3799ac999b804108204024022e0e1427607bb Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 16 Oct 2026 13:30:00 -0600 Subject: [PATCH 05/11] Preserve matching staging CI identity and refresh synthetic sessions --- scripts/check-worker-staging-auth-browser.mjs | 10 ++++++++++ scripts/deploy-worker-staging.mjs | 8 +++++++- scripts/seed-worker-staging.ts | 7 ++++--- staging/README.md | 6 +++++- tests/worker-deployment.test.ts | 15 +++++++++++++-- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/scripts/check-worker-staging-auth-browser.mjs b/scripts/check-worker-staging-auth-browser.mjs index a96e2881c..fece7df29 100644 --- a/scripts/check-worker-staging-auth-browser.mjs +++ b/scripts/check-worker-staging-auth-browser.mjs @@ -56,6 +56,16 @@ const sessions = JSON.parse(fs.readFileSync('.staging/sessions.json', 'utf8')) origin + '/api/chat/account/preferences', ) assert.equal(signedOut.status(), 401) + const revoked = await context.request.get( + origin + '/api/chat/account/preferences', + { + headers: { + Cookie: + 'session_token=' + encodeURIComponent(sessions.users[0].token), + }, + }, + ) + assert.equal(revoked.status(), 401) assert.deepEqual(errors, []) assert.deepEqual(forbiddenRequests, []) const result = { diff --git a/scripts/deploy-worker-staging.mjs b/scripts/deploy-worker-staging.mjs index df6186882..77dd8cd50 100644 --- a/scripts/deploy-worker-staging.mjs +++ b/scripts/deploy-worker-staging.mjs @@ -56,6 +56,12 @@ const env = { ...process.env } delete env.WRANGLER_CI_OVERRIDE_NAME delete env.WRANGLER_CI_MATCH_TAG for (const { file, config } of configurations) { + const workerEnv = { ...env } + if (process.env.WRANGLER_CI_OVERRIDE_NAME === config.name) { + workerEnv.WRANGLER_CI_OVERRIDE_NAME = config.name + if (process.env.WRANGLER_CI_MATCH_TAG) + workerEnv.WRANGLER_CI_MATCH_TAG = process.env.WRANGLER_CI_MATCH_TAG + } console.log(`Deploying staging Worker ${config.name}`) const result = spawnSync( 'pnpm', @@ -69,7 +75,7 @@ for (const { file, config } of configurations) { config.name, '--keep-vars', ], - { env, stdio: 'inherit' }, + { env: workerEnv, stdio: 'inherit' }, ) if (result.error) throw result.error if (result.status !== 0) throw new Error(`Deployment failed: ${config.name}`) diff --git a/scripts/seed-worker-staging.ts b/scripts/seed-worker-staging.ts index 115403e2c..d08ebeaec 100644 --- a/scripts/seed-worker-staging.ts +++ b/scripts/seed-worker-staging.ts @@ -15,16 +15,17 @@ try { for (const name of ['Alice', 'Bob']) { const candidateId = randomUUID() const [user] = - await sql`insert into users (id,email,name) values (${candidateId},${'staging-' + name.toLowerCase() + '@example.invalid'},${'Staging ' + name}) ON CONFLICT (email) DO UPDATE SET name=EXCLUDED.name RETURNING id` + await sql`insert into users (id,email,name) values (${candidateId},${'staging-' + name.toLowerCase() + '@example.invalid'},${'Staging ' + name}) ON CONFLICT (email) DO UPDATE SET name=EXCLUDED.name RETURNING id,session_version` const id = user.id await sql`insert into chat_access (user_id) values (${id}) ON CONFLICT (user_id) DO NOTHING` users.push({ id, name, + version: user.session_version, token: await service.signCookie({ userId: id, expiresAt: Date.now() + 86400000, - version: 0, + version: user.session_version, }), }) } @@ -36,7 +37,7 @@ try { const revoked = await service.signCookie({ userId: users[0].id, expiresAt: Date.now() + 86400000, - version: 1, + version: users[0].version + 1, }) const missing = await service.signCookie({ userId: randomUUID(), diff --git a/staging/README.md b/staging/README.md index cf984c08a..cc04e59a5 100644 --- a/staging/README.md +++ b/staging/README.md @@ -14,7 +14,7 @@ project persistence are outside this public docs comparison. Build with `pnpm build:staging`, deploy with `pnpm deploy:staging`. The deployment preflight checks every destination and resource binding before uploading anything. It removes `WRANGLER_CI_OVERRIDE_NAME` and -`WRANGLER_CI_MATCH_TAG` from child Wrangler processes and supplies each name +`WRANGLER_CI_MATCH_TAG` from sibling Worker processes and supplies each name explicitly. The installed Wrangler overrides even `--name` when that CI name variable is present. @@ -101,3 +101,7 @@ These tests use synthetic users and valid sessions signed by the existing SessionService with staging-only keys. They do not test GitHub/Google OAuth consent and callbacks, model inference, or Kody integrations. No production OAuth or model credentials were copied. + +The matching staging site retains its CI name and tag so Cloudflare can associate +its version with the build and verify its identity. Sibling Workers receive +neither variable. An unrelated CI name is removed from every deployment. diff --git a/tests/worker-deployment.test.ts b/tests/worker-deployment.test.ts index 4ad113314..65fe14591 100644 --- a/tests/worker-deployment.test.ts +++ b/tests/worker-deployment.test.ts @@ -12,7 +12,7 @@ import path from 'node:path' import { spawnSync } from 'node:child_process' import test from 'node:test' -function fixture(productionBinding = false) { +function fixture(productionBinding = false, ciWorker = 'tanstack-com') { const directory = mkdtempSync(path.join(tmpdir(), 'staging-deployment-')) const bin = path.join(directory, 'bin') mkdirSync(bin) @@ -46,7 +46,7 @@ fs.appendFileSync('calls.jsonl', JSON.stringify({args:process.argv.slice(2),over env: { ...process.env, PATH: bin + path.delimiter + process.env.PATH, - WRANGLER_CI_OVERRIDE_NAME: 'tanstack-com', + WRANGLER_CI_OVERRIDE_NAME: ciWorker, WRANGLER_CI_MATCH_TAG: 'production-tag', }, encoding: 'utf8', @@ -88,3 +88,14 @@ test('a production binding aborts the entire fleet before the first upload', () assert.match(result.stderr, /Non-staging service binding/) assert.deepEqual(calls, []) }) + +test('only the matching staging Worker retains its CI attribution and tag verification', () => { + const { result, calls } = fixture(false, 'tanstack-perf-stage-site') + assert.equal(result.status, 0, result.stderr) + for (const call of calls.slice(0, 3)) { + assert.equal(call.override, undefined) + assert.equal(call.tag, undefined) + } + assert.equal(calls[3].override, 'tanstack-perf-stage-site') + assert.equal(calls[3].tag, 'production-tag') +}) From 399d11e580a6efc6a9a3341a3b701b8c9ac45dfe Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 16 Oct 2026 13:33:09 -0600 Subject: [PATCH 06/11] Record successful isolated Cloudflare Builds and authentication verification --- staging/cloudflare-builds.json | 43 +++++++++++++++++++++++++ staging/cloudflare-deployments.json | 38 ++++++++++++++++++++++ staging/state-authenticated-stage.json | 44 ++++++++++++++++++++++++++ staging/state-final-build.json | 44 ++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 staging/cloudflare-builds.json create mode 100644 staging/cloudflare-deployments.json create mode 100644 staging/state-authenticated-stage.json create mode 100644 staging/state-final-build.json diff --git a/staging/cloudflare-builds.json b/staging/cloudflare-builds.json new file mode 100644 index 000000000..e9981719d --- /dev/null +++ b/staging/cloudflare-builds.json @@ -0,0 +1,43 @@ +{ + "repository": "TanStack/tanstack-com-worker-staging", + "worker": "tanstack-perf-stage-site", + "branch": "main", + "buildCommand": "pnpm build:staging", + "deployCommand": "pnpm deploy:staging", + "previewBuildsEnabled": false, + "completedBuilds": [ + "89710348-86be-41a7-b1b4-76fc8174489e", + "65689645-a2b9-42c2-93da-6636b2e3cf58", + "e411366e-2004-4113-b4cb-1f9a52c02049", + "39c8aba7-a6a5-4b36-b2d9-4c6f17c50962" + ], + "finalBuild": "39c8aba7-a6a5-4b36-b2d9-4c6f17c50962", + "finalCommit": "8ec3799ac999b804108204024022e0e1427607bb", + "database": { + "projectId": "spring-dew-83008577", + "name": "worker_staging_auth", + "tables": 103, + "triggers": 35, + "migrations": 42, + "productionDataCopied": false, + "builderSchemaVerified": true, + "chatSchemaVerified": true + }, + "oauthHandshakeTested": false, + "modelInferenceTested": false, + "productionModified": false, + "observedCiWorker": "tanstack-perf-stage-site", + "observedCiMatchTagPresent": true, + "observedDestinations": [ + "tanstack-perf-stage-og", + "tanstack-perf-stage-starter", + "tanstack-perf-stage-chat", + "tanstack-perf-stage-site" + ], + "finalBuildStatus": "success", + "finalSiteVersion": "15525c51-3af6-4b4f-b21f-93bcdfdb89c6", + "namespaceIdsPreserved": true, + "authenticatedChecksPassed": 13, + "signedInBrowserPassed": true, + "oldSessionRejectedAfterLogout": true +} diff --git a/staging/cloudflare-deployments.json b/staging/cloudflare-deployments.json new file mode 100644 index 000000000..02fc23095 --- /dev/null +++ b/staging/cloudflare-deployments.json @@ -0,0 +1,38 @@ +{ + "tanstack-perf-stage-site": { + "created_on": "2026-10-16T19:31:57.055694Z", + "versions": [ + { + "version_id": "15525c51-3af6-4b4f-b21f-93bcdfdb89c6", + "percentage": 100 + } + ] + }, + "tanstack-perf-stage-chat": { + "created_on": "2026-10-16T19:31:38.903519Z", + "versions": [ + { + "version_id": "2d24dac4-da11-4d58-b4cc-343e67377141", + "percentage": 100 + } + ] + }, + "tanstack-perf-stage-starter": { + "created_on": "2026-10-16T19:31:32.825413Z", + "versions": [ + { + "version_id": "6fec3e93-cf2e-4035-bd5f-2f8e1a284450", + "percentage": 100 + } + ] + }, + "tanstack-perf-stage-og": { + "created_on": "2026-10-16T19:31:26.481508Z", + "versions": [ + { + "version_id": "2f58b9a9-a34b-472f-b814-239589a268b9", + "percentage": 100 + } + ] + } +} diff --git a/staging/state-authenticated-stage.json b/staging/state-authenticated-stage.json new file mode 100644 index 000000000..e4365e7c5 --- /dev/null +++ b/staging/state-authenticated-stage.json @@ -0,0 +1,44 @@ +[ + { + "id": "4651e38f155643828ca053285f505d5d", + "name": "tanstack-perf-stage-chat_StreamObject", + "script": "tanstack-perf-stage-chat", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "4bc05d1c72ab4aa3ad405020b0fe65be", + "name": "tanstack-perf-stage-chat_Conversation", + "script": "tanstack-perf-stage-chat", + "class": "Conversation", + "use_sqlite": true + }, + { + "id": "f03cfa387d274727b998314a531346b4", + "name": "tanstack-perf-stage-chat_WorkspaceSync", + "script": "tanstack-perf-stage-chat", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "bc6bf2ef2daa48cca297edff98348661", + "name": "tanstack-perf-stage-baseline_StreamObject", + "script": "tanstack-perf-stage-baseline", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "3e7aa9a74e2349029e8612ae77fd3d82", + "name": "tanstack-perf-stage-baseline_WorkspaceSync", + "script": "tanstack-perf-stage-baseline", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "83ac8130974a4d3e91e11485a073bb5c", + "name": "tanstack-perf-stage-baseline_Conversation", + "script": "tanstack-perf-stage-baseline", + "class": "Conversation", + "use_sqlite": true + } +] diff --git a/staging/state-final-build.json b/staging/state-final-build.json new file mode 100644 index 000000000..e4365e7c5 --- /dev/null +++ b/staging/state-final-build.json @@ -0,0 +1,44 @@ +[ + { + "id": "4651e38f155643828ca053285f505d5d", + "name": "tanstack-perf-stage-chat_StreamObject", + "script": "tanstack-perf-stage-chat", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "4bc05d1c72ab4aa3ad405020b0fe65be", + "name": "tanstack-perf-stage-chat_Conversation", + "script": "tanstack-perf-stage-chat", + "class": "Conversation", + "use_sqlite": true + }, + { + "id": "f03cfa387d274727b998314a531346b4", + "name": "tanstack-perf-stage-chat_WorkspaceSync", + "script": "tanstack-perf-stage-chat", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "bc6bf2ef2daa48cca297edff98348661", + "name": "tanstack-perf-stage-baseline_StreamObject", + "script": "tanstack-perf-stage-baseline", + "class": "StreamObject", + "use_sqlite": true + }, + { + "id": "3e7aa9a74e2349029e8612ae77fd3d82", + "name": "tanstack-perf-stage-baseline_WorkspaceSync", + "script": "tanstack-perf-stage-baseline", + "class": "WorkspaceSync", + "use_sqlite": true + }, + { + "id": "83ac8130974a4d3e91e11485a073bb5c", + "name": "tanstack-perf-stage-baseline_Conversation", + "script": "tanstack-perf-stage-baseline", + "class": "Conversation", + "use_sqlite": true + } +] From 066d4753e9c06716e764f23fa8ca0cd9829a59b1 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 16 Oct 2026 05:25:01 -0600 Subject: [PATCH 07/11] Label application starter prompt fields for assistive technology (#1389) * Label application starter prompt fields for assistive technology * Include visible prompt text in accessible names --- src/components/ApplicationStarter.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ApplicationStarter.tsx b/src/components/ApplicationStarter.tsx index 7e38f8a3e..29a2032a8 100644 --- a/src/components/ApplicationStarter.tsx +++ b/src/components/ApplicationStarter.tsx @@ -837,6 +837,7 @@ export function ApplicationStarter({ Prompt