-
-
Notifications
You must be signed in to change notification settings - Fork 402
Add server-powered dashboard reference with Neon and R2 #1246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tannerlinsley
wants to merge
3
commits into
main
Choose a base branch
from
taren/dashboard-neon
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| { | ||
| "measuredAt": "2026-09-09T23:48:14.636Z", | ||
| "database": "PostgreSQL 17, local single connection", | ||
| "rows": 1000832, | ||
| "fixture": "112 copies of the same week, unique IDs; synthetic, no new data diversity", | ||
| "warmupRuns": 1, | ||
| "measuredRuns": 3, | ||
| "results": [ | ||
| { | ||
| "name": "overview", | ||
| "matched": 1000832, | ||
| "samplesMs": [541, 469, 477] | ||
| }, | ||
| { | ||
| "name": "borough-day", | ||
| "matched": 25760, | ||
| "samplesMs": [84, 80, 120] | ||
| }, | ||
| { | ||
| "name": "substring", | ||
| "matched": 46256, | ||
| "samplesMs": [2531, 2582, 2368] | ||
| }, | ||
| { | ||
| "name": "late-page", | ||
| "matched": 1000832, | ||
| "samplesMs": [710, 706, 697] | ||
| }, | ||
| { | ||
| "name": "fare-sort", | ||
| "matched": 1000832, | ||
| "samplesMs": [452, 499, 444] | ||
| }, | ||
| { | ||
| "name": "group-day", | ||
| "matched": 1000832, | ||
| "samplesMs": [590, 572, 697] | ||
| }, | ||
| { | ||
| "name": "all-selected", | ||
| "matched": 1000832, | ||
| "samplesMs": [507, 508, 483] | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| { | ||
| "browser": "152.0.7977.83", | ||
| "viewport": "1440x1200, then 390x844", | ||
| "mode": "Vite development, no CPU throttle, no simulated delay", | ||
| "requests": 1, | ||
| "hardware": { | ||
| "platform": "darwin", | ||
| "arch": "arm64", | ||
| "cpu": "Apple M5 Pro" | ||
| }, | ||
| "rendering": [ | ||
| { | ||
| "name": "LayoutCount", | ||
| "delta": 55 | ||
| }, | ||
| { | ||
| "name": "LayoutDuration", | ||
| "delta": 0.035796 | ||
| }, | ||
| { | ||
| "name": "RecalcStyleDuration", | ||
| "delta": 0.059109 | ||
| }, | ||
| { | ||
| "name": "ScriptDuration", | ||
| "delta": 0.426704 | ||
| }, | ||
| { | ||
| "name": "TaskDuration", | ||
| "delta": 0.6941269999999999 | ||
| } | ||
| ], | ||
| "materialFocusRestored": false, | ||
| "loadedHeap": { | ||
| "usedSize": 51581008, | ||
| "totalSize": 53641216, | ||
| "embedderHeapUsedSize": 14724592, | ||
| "backingStorageSize": 25070981 | ||
| }, | ||
| "finalHeap": { | ||
| "usedSize": 62258304, | ||
| "totalSize": 65896448, | ||
| "embedderHeapUsedSize": 15761920, | ||
| "backingStorageSize": 25853802 | ||
| }, | ||
| "metrics": [ | ||
| { | ||
| "name": "dashboard:fetch", | ||
| "duration": 4.099999904632568 | ||
| }, | ||
| { | ||
| "name": "dashboard:parse-and-validate", | ||
| "duration": 6.800000071525574 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0.3999999761581421 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0.2999999523162842 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0.20000004768371582 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0.20000004768371582 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0.30000007152557373 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0 | ||
| }, | ||
| { | ||
| "name": "dashboard:derive", | ||
| "duration": 0.10000002384185791 | ||
| } | ||
| ], | ||
| "errors": [] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| # Neon and R2 deployment | ||
|
|
||
| ## Prepared in this task | ||
|
|
||
| - Verified access to the site's TanStack Cloudflare account. | ||
| - Created the separate Standard R2 bucket `tanstack-dashboard-demo`. | ||
| - Uploaded `green-2025-week1.v1/snapshot.json` and `green-2025-week1.v1/trips.csv`. | ||
| - Added the `DASHBOARD_DEMO` binding to the site's Wrangler configuration. | ||
| - Added a validated, bounded R2 response cache with a local edge-cache layer. | ||
| - Added `DASHBOARD_HYPERDRIVE` support for the dedicated demo database. | ||
|
|
||
| The staging verification below was completed before production publication. | ||
| Hosted resources use the existing TanStack accounts: | ||
|
|
||
| - Neon organization: `org-green-water-28502066`. | ||
| - Demo project: `patient-field-88705156` (`tanstack-dashboard-demo`). | ||
| - Database: `dashboard_demo`, PostgreSQL 17, AWS Ohio (`aws-us-east-2`). | ||
| - Compute: fixed at 0.25 CU, with five-minute idle autosuspend. | ||
| - Dataset: 8,936 reference trips, imported and verified using the runtime role. | ||
| - Runtime role: `dashboard_reader`, SELECT access only, 20-connection role limit, | ||
| 15-second statement timeout, and read-only transactions by default. | ||
| - Hyperdrive: `07bd2803c2c240ad81681a2233b88de4`, bound as | ||
| `DASHBOARD_HYPERDRIVE`, with a soft origin connection limit of five and SQL | ||
| caching disabled. The application owns response caching in R2. | ||
|
|
||
| Hyperdrive uses its default `require` TLS mode, which validates server certificates | ||
| against WebPKI. Custom `verify-full` configuration requires uploading a CA | ||
| certificate. The origin is the direct Neon endpoint, not its pooled endpoint. | ||
|
|
||
| The main checkout's existing owner credentials were verified, but the demo uses | ||
| its own project, database, and runtime credentials. The ignored local `.dev.vars` | ||
| contains the demo reader connection and the Hyperdrive local connection override. | ||
| Owner credentials were not saved in the Worker configuration. | ||
|
|
||
| The user confirmed that Neon subsidizes the organization's databases. The console | ||
| shows the existing Launch plan, but the precise subsidy terms and expiry have not | ||
| been independently verified. Compute limits do not constitute a total spending cap. | ||
|
|
||
| ## Reproducing the database setup | ||
|
|
||
| Create a separate demo project/compute so demo load cannot keep the production | ||
| compute busy. Confirm that new projects are covered by the subsidy. Choose a small | ||
| compute with autosuspend enabled and a bounded autoscaling maximum. Start with the | ||
| 8,936-record reference dataset; the million-row synthetic benchmark is not the | ||
| public dataset and should not be uploaded by accident. | ||
|
|
||
| Seed using an owner connection, apply the runtime role grants, and verify read-only | ||
| access with that role. Keep owner credentials out of the Worker. Configure a | ||
| Cloudflare Hyperdrive instance named `tanstack-dashboard-demo` using the runtime | ||
| role and the dedicated Neon origin. Add its returned ID as `DASHBOARD_HYPERDRIVE` | ||
| in Wrangler; do not reuse the site's Hyperdrive binding. Use Cloudflare's current | ||
| Neon/Hyperdrive instructions to choose the origin endpoint and TLS settings. | ||
| Hyperdrive pools connections; PostgreSQL role limits and timeouts provide additional | ||
| bounds. Evaluate connection-pool behavior against Neon's autosuspend metrics. | ||
|
|
||
| Local and standalone Node development can continue using `DASHBOARD_DATABASE_URL`. | ||
| Hosted Workers prefer the dedicated Hyperdrive connection string. Both query and | ||
| CSV code use the same connection resolver. | ||
|
|
||
| ## Cache behavior and limits | ||
|
|
||
| The persistent cache is intentionally finite: eight day choices, eight borough | ||
| choices, the first three pages, and four page sizes. Only the default pickup sort, | ||
| no zone, no text/column filters, no grouping, no detail ID, and no row selection | ||
| qualify. There are at most 768 keys per response/data version. Invalid borough | ||
| strings cannot manufacture additional keys. | ||
|
|
||
| A Worker checks its local edge cache, then R2, then PostgreSQL. Successful misses | ||
| populate R2 and the local cache. Cached JSON is validated before use. Corrupt entries | ||
| are recomputed and overwritten. Cache read/write failures are logged and do not | ||
| prevent the database path from serving a response. Logs report hit/miss/error only, | ||
| without request data or credentials. Cache hits do not open a database connection. | ||
|
|
||
| Arbitrary searches and visitor selections bypass persistence. This avoids unbounded | ||
| object growth and keeps selection state out of common results. They currently still | ||
| compute a full response in PostgreSQL. Separating their calculation from reusable | ||
| aggregates is a future optimization, not an implemented guarantee. | ||
|
|
||
| Concurrent cold misses can still compute the same key. Edge entries are local to | ||
| a data center and may be evicted. Neither cache is a hard request or spending cap. | ||
| Before public rollout, configure dashboard-specific abuse controls and usage alerts; | ||
| watch cache misses, PostgreSQL active time, latency, and connections. The 15-second | ||
| query timeout and role connection limits remain in effect on uncached work. | ||
|
|
||
| This policy is only for this public immutable dataset. Change the dataset prefix | ||
| when changing the dataset, and the response version when changing SQL or response | ||
| semantics. Remove obsolete R2 prefixes deliberately after rollout. Do not apply this | ||
| cache unchanged to private, mutable, or tenant-specific data. | ||
|
|
||
| ## Downloads | ||
|
|
||
| Unfiltered full CSV exports with the default sort read the prepared R2 object. | ||
| Filtered, selected, and differently sorted exports still run the streaming database | ||
| query. The source JSON also lives in R2; the existing static client snapshot URL | ||
| remains intact. The bucket is not publicly listed or exposed through a public domain. | ||
| The Worker serves downloads using its binding. | ||
|
|
||
| ## Rollout checks | ||
|
|
||
| 1. Confirm the subsidy terms and expiry before public rollout. The project, region, | ||
| compute limits, dataset, runtime grants, and Hyperdrive binding are configured. | ||
| 2. Hosted SQL checks passed for the full 8,936-row dataset and the 43-row | ||
| Queens / January 1 / Forest Hills search. Runtime write and schema-create | ||
| privileges were verified absent. | ||
| 3. Test staging against the hosted database and R2. Verify repeated common requests | ||
| produce cache hits without database timing logs, and selection/search stay correct. | ||
| 4. Check full CSV record count and default ordering against the source fixture. | ||
| 5. Set request controls and usage alerts, then deploy the site through its normal flow. | ||
|
|
||
| References: [Hyperdrive PostgreSQL](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/), | ||
| [R2 Workers API](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/), | ||
| [Worker cache locality](https://developers.cloudflare.com/workers/runtime-apis/cache/). | ||
|
|
||
| TLS reference: [Hyperdrive TLS](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/). | ||
|
|
||
| ## Hosted staging verification, September 10, 2026 | ||
|
|
||
| Version `5fa88303-ee9e-4d47-8dba-62ba191ec7d6` was tested before receiving | ||
| production traffic. Native, Material UI, and shadcn/Base UI each passed in server | ||
| and client modes, including pagination, keyboard navigation, column resizing, | ||
| selection reset, search, grouping, CSV export, retry, and offline recovery. | ||
| The default dashboard rendered 8,936 trips. Full CSV export returned 8,936 records | ||
| and the prepared R2 object's ETag. The default response cache object was retrieved | ||
| from R2 after the requests. Preview tailing did not expose cache event logs, so | ||
| per-request database avoidance was verified by the cache tests rather than claimed | ||
| from live logs. | ||
|
|
||
| The release was rebased onto the current site changes before building. The full | ||
| suite passed with 524 tests and one skipped test. The production build and Worker | ||
| binding validation passed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Deploying the dashboard server | ||
|
|
||
| No hosted infrastructure is provisioned by this example. Use a separate PostgreSQL | ||
| database and provision `DASHBOARD_DATABASE_URL` through your hosting provider's | ||
| secret store. Never reuse the site's production database or commit credentials. | ||
|
|
||
| ## Database access and limits | ||
|
|
||
| Apply `schema.sql` and seed as the owner. `server/runtime-role.sql` creates a | ||
| runtime login with SELECT access, a 20-connection ceiling, a 15-second default | ||
| statement timeout, a 30-second idle transaction timeout, and read-only transactions. | ||
| Run it once and provision its password through your provider or `psql`'s | ||
| `\password dashboard_reader`. Use TLS and your provider's certificate settings. | ||
| The connection limit is an initial deployment budget, not a universal tuning value. | ||
|
|
||
| Each dashboard request opens one connection, runs a repeatable-read transaction, | ||
| and closes it in `finally`. CSV export uses one connection and a cursor fetching | ||
| 500 rows at a time. This bounds connections per request, not globally across | ||
| Workers. Use a provider pooler and tune database role limits, platform concurrency, | ||
| and request rate limits together. A single-process JavaScript semaphore would not | ||
| protect a distributed deployment. The sample data is public; adding private data | ||
| requires authentication and authorization at both RPC and export boundaries. | ||
|
|
||
| Database failures produce a retryable user message without exposing SQL or driver | ||
| errors. Structured `dashboard.database` logs record total duration, statement count, | ||
| and success without filters, IDs, credentials, or query text. Monitor failures, | ||
| latency percentiles, connection saturation, and exported response volume. | ||
| Superseded browser requests consume AbortSignal; SQL has its own timeout. Browser | ||
| cancellation does not promise immediate PostgreSQL cancellation. | ||
|
|
||
| ## Downloads | ||
|
|
||
| Server exports submit a native POST form to the CSV endpoint. Download content is | ||
| streamed by the browser, with no client-side Blob accumulation. JSON POST remains | ||
| supported for API clients. The route validates both formats, rejects malformed | ||
| input and cross-origin browser submissions, and returns an actionable 503 if the | ||
| initial transaction cannot start. Responses use attachment and no-store headers. | ||
| If a stream fails after response headers, the browser reports a failed or interrupted | ||
| download; an HTTP status cannot be changed at that point. Export statements use a | ||
| 60-second timeout. Larger jobs need a queue and object storage with expiring links. | ||
| Client mode still creates a Blob because its entire dataset is already local. | ||
|
|
||
| ## Benchmarks | ||
|
|
||
| `benchmark-results.json` records the measured one-million-row PostgreSQL 17 run. | ||
| It uses 112 copies of the same week with distinct IDs, not a production-like mix | ||
| of dates, distributions, or tenants. There is one connection, one warm-up request, | ||
| and three timed requests per case. Timings include all SQL for a complete dashboard | ||
| response, not network or browser render time. These are local measurements, not | ||
| service-level guarantees or concurrent-load results. | ||
|
|
||
| The measured search path was the slowest, around 2.4 to 2.6 seconds. It searches | ||
| multiple fields including formatted numeric values. Before scaling that workflow, | ||
| define the intended search fields and choose appropriate full-text or trigram | ||
| indexes. Deep offset pagination, exact counts, and percentile aggregation also | ||
| need workload-specific evaluation. Keyset paging changes the UI contract and is | ||
| not silently substituted for page numbers. | ||
|
|
||
| To reproduce, initialize a disposable PostgreSQL database, seed its 8,936 rows, | ||
| then run `DASHBOARD_BENCHMARK_DATABASE_URL=... pnpm dashboard:benchmark`. The script | ||
| requires the initial fixture row count and expands it to 1,000,832 rows. It never | ||
| reads the site's database binding. Destroy only that disposable database afterward. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the obsolete infrastructure state. These sections still describe the dashboard before its dedicated hosted resources were provisioned.
docs/dashboard/production.md#L3-L5: clarify that extraction does not provision infrastructure, while the repository demo has hosted resources.docs/dashboard/server.md#L36-L39: replace the no-hosted-database statement with a link todocs/dashboard/hosting.md.📍 Affects 2 files
docs/dashboard/production.md#L3-L5(this comment)docs/dashboard/server.md#L36-L39🤖 Prompt for AI Agents