-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add fullstack-vercel-turso example #105
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
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
0f89efd
feat: add fullstack-vercel-turso example
coji 8fec299
chore: remove swp file and add *.swp to gitignore
coji 1359e95
fix: use fixed step numbers and add missing GenerateReport type
coji 9183553
fix: add explicit framework and build config to vercel.json
coji 3956cb3
fix: fix Vercel deployment and polish example
coji e89a70e
refactor: extract shared delay helper and remove redundant API call
coji 048a8c7
fix: add missing durably.init() in action and clean up
coji a9f5cdb
fix: use turbo for build to include workspace dependencies
coji a0cbf8b
fix: address CodeRabbit review feedback
coji d33bdfb
revert: remove preserveSteps (not desired for this example)
coji 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,4 +12,6 @@ website/.vitepress/cache/ | |
| website/.vitepress/dist/ | ||
|
|
||
| # test coverage | ||
| coverage | ||
| coverage | ||
| .vercel | ||
| .env*.local | ||
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,7 @@ | ||
| # Local development (libsqld via Docker) | ||
| TURSO_DATABASE_URL=http://localhost:8080 | ||
|
|
||
| # Production (Turso) | ||
| # TURSO_DATABASE_URL=libsql://your-db-name.turso.io | ||
| # TURSO_AUTH_TOKEN=your-token | ||
| # CRON_SECRET=your-secret |
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,11 @@ | ||
| .DS_Store | ||
| .env | ||
| /node_modules/ | ||
|
|
||
| # React Router | ||
| /.react-router/ | ||
| /build/ | ||
|
|
||
| *.swp | ||
| .vercel | ||
| .env*.local |
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,13 @@ | ||
| # Exclude sibling examples to prevent @vercel/react-router builder | ||
| # from scanning other React Router apps in the monorepo | ||
| ../../examples/fullstack-react-router | ||
| ../../examples/spa-vite-react | ||
| ../../examples/spa-react-router | ||
| ../../examples/server-node | ||
|
|
||
| # Exclude non-essential workspace packages | ||
| ../../website | ||
|
|
||
| # Exclude tests and docs | ||
| ../../packages/durably/tests | ||
| ../../packages/durably-react/tests |
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,78 @@ | ||
| # Fullstack Vercel + Turso Example | ||
|
|
||
| Durably fullstack demo deployed on Vercel with Turso (libSQL) as the database. | ||
|
|
||
| > **Note**: This is a demo app with no authentication on the Durably control plane. | ||
| > For production use, add authentication via `createDurablyHandler({ authenticate })`. | ||
| > See the [auth guide](../../website/guide/auth.md) for details. | ||
|
|
||
| ## Architecture | ||
|
|
||
| - **Framework**: React Router v7 with `@vercel/react-router` preset | ||
| - **Database**: Turso (remote libSQL) in production, local libsqld via Docker in development | ||
| - **Worker**: Dual-mode | ||
| - **Real-time**: `onRequest` lazily starts the worker — runs during SSE streaming | ||
| - **Background**: `/api/worker` endpoint called by Vercel Cron (requires Pro plan for per-minute schedule) | ||
|
|
||
| ## How it works | ||
|
|
||
| ```text | ||
| User triggers job → POST /api/durably/trigger | ||
| ↓ | ||
| User subscribes → GET /api/durably/subscribe?runId=xxx (SSE) | ||
| ↓ | ||
| onRequest → durably.init() starts worker during SSE connection | ||
| ↓ | ||
| Worker processes → steps stream via SSE in real-time | ||
| ↓ | ||
| SSE disconnects → function terminates, worker stops | ||
| ↓ | ||
| Vercel Cron → GET /api/worker processes any remaining pending jobs | ||
| ``` | ||
|
|
||
| ## Setup | ||
|
|
||
| ### Local development | ||
|
|
||
| Requires Docker for the local libsqld instance. | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| pnpm install | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| This starts a local libsqld container (Docker) and the dev server. | ||
| The app connects to `http://localhost:8080` — same HTTP protocol as production Turso. | ||
|
|
||
| ### Production (Vercel + Turso) | ||
|
|
||
| 1. Create a Turso database: | ||
|
|
||
| ```bash | ||
| turso db create my-durably-app | ||
| turso db tokens create my-durably-app | ||
| ``` | ||
|
|
||
| 2. Set environment variables in Vercel: | ||
| - `TURSO_DATABASE_URL` — `libsql://my-durably-app-<user>.turso.io` | ||
| - `TURSO_AUTH_TOKEN` — token from step 1 | ||
| - `CRON_SECRET` — any random string to authenticate cron requests | ||
|
|
||
| 3. Deploy: | ||
| ```bash | ||
| vercel | ||
| ``` | ||
|
|
||
| ## Key files | ||
|
|
||
| | File | Description | | ||
| | ----------------------------- | ------------------------------------------- | | ||
| | `docker-compose.yml` | Local libsqld for development | | ||
| | `app/lib/database.server.ts` | Turso/libSQL connection config | | ||
| | `app/lib/durably.server.ts` | Durably instance with `onRequest` lazy init | | ||
| | `app/lib/durably.ts` | Type-safe client for React components | | ||
| | `app/routes/api.durably.$.ts` | Durably HTTP/SSE handler (splat route) | | ||
| | `app/routes/api.worker.ts` | Background worker endpoint for Vercel Cron | | ||
| | `vercel.json` | Cron schedule (per-minute requires Pro) | | ||
| | `react-router.config.ts` | Vercel preset configuration | |
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,12 @@ | ||
| @import 'tailwindcss'; | ||
|
|
||
| @theme { | ||
| --font-sans: | ||
| 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', | ||
| 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; | ||
| } | ||
|
|
||
| html, | ||
| body { | ||
| @apply bg-gray-50; | ||
| } |
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,55 @@ | ||
| /** | ||
| * Data Sync Job | ||
| * | ||
| * Simulates syncing data with a remote server. | ||
| */ | ||
|
|
||
| import { defineJob } from '@coji/durably' | ||
| import { z } from 'zod' | ||
| import { delay } from './delay' | ||
|
|
||
| export const dataSyncJob = defineJob({ | ||
| name: 'data-sync', | ||
| input: z.object({ userId: z.string() }), | ||
| output: z.object({ synced: z.number(), failed: z.number() }), | ||
| run: async (step, input) => { | ||
| step.log.info(`Starting sync for user: ${input.userId}`) | ||
|
|
||
| const items = await step.run('fetch-local', async () => { | ||
| step.progress(1, 4, 'Fetching local data...') | ||
| await delay(300) | ||
| return Array.from({ length: 10 }, (_, i) => ({ | ||
| id: `item-${i}`, | ||
| data: `Data for ${input.userId}`, | ||
| })) | ||
| }) | ||
|
|
||
| let synced = 0 | ||
| let failed = 0 | ||
|
|
||
| for (let i = 0; i < items.length; i++) { | ||
| const item = items[i] | ||
| const success = await step.run(`sync-item-${item.id}`, async () => { | ||
| step.progress(2 + Math.floor(i / 5), 4, `Syncing item ${i + 1}...`) | ||
| await delay(100) | ||
| return Math.random() > 0.1 // 90% success rate | ||
| }) | ||
|
|
||
| if (success) { | ||
| synced++ | ||
| } else { | ||
| failed++ | ||
| step.log.warn(`Failed to sync item: ${item.id}`) | ||
| } | ||
| } | ||
|
|
||
| await step.run('finalize', async () => { | ||
| step.progress(4, 4, 'Finalizing...') | ||
| await delay(200) | ||
| }) | ||
|
|
||
| step.log.info(`Sync complete: ${synced} synced, ${failed} failed`) | ||
|
|
||
| return { synced, failed } | ||
| }, | ||
| }) | ||
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 @@ | ||
| export const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)) |
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.
Uh oh!
There was an error while loading. Please reload this page.
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.
🧩 Analysis chain
🏁 Script executed:
Repository: coji/durably
Length of output: 4881
Add
preserveSteps: trueto preserve step history for the dashboard.The dashboard fetches and displays step history for completed runs, but this job uses the default cleanup behavior that deletes steps when runs reach a terminal state. Add
preserveSteps: trueto the job definition to preserve step records, and apply the same setting to the other example jobs the dashboard displays.🤖 Prompt for AI Agents
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.
This is intentional — we don't want to preserve steps for this demo. Step data is cleaned up on terminal state by design (default behavior). The dashboard's View button fetches steps, which will show empty for completed runs, and that's fine for demonstrating the default.
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.
@coji, understood! Demonstrating the default cleanup behavior is a perfectly valid choice for this example — showing that step history is empty for completed runs is itself informative for users exploring the defaults.✏️ Learnings added
🧠 Learnings used