Skip to content

mw10013/tanstack-cloudflare-effect-saas

Repository files navigation

TanStack Cloudflare Effect SaaS

Lightweight invoice template packed with essential functionality for TanStack, Cloudflare, and Effect

TanStack • Cloudflare • Effect • Better Auth • Stripe • Shadcn on Base UI

Demo

Stack

  • TanStack: Start, Router, Query, Form
  • Cloudflare: D1, DO, Agent, Workflow, Queue, KV, Cron, Rate Limiting, Web Analytics
  • Effect: v4
  • Better Auth: Magic Link, Admin, Organization, Stripe
  • UI: Shadcn on Base UI
  • Testing: Vitest, Cloudflare Vitest-Integration, Playwright

Template Functionality

Invoices are a vehicle to exercise Cloudflare primitives, Effect v4, and fault-tolerant eventual consistency patterns under at-least-once delivery, partial failures, and crashes.

  • Cloudflare Services:

    • D1 (SQLite) for Better Auth + app data with idempotent-write retry
    • Durable Object Agents (OrganizationAgent) per organization with DO-local SQLite, @callable RPC, and WebSocket broadcast
    • Workflows: UserProvisioningWorkflow (Better Auth org/member reconciliation) and InvoiceExtractionWorkflow (AgentWorkflow: R2 load → LLM extract → DO save)
    • Queues for R2 event notifications and durable finalization safety nets
    • R2 object storage with event notifications and retry on retryable codes (10001/10043/10054/10058)
    • KV with exponential backoff + jitter
    • Cron Trigger for expired-session cleanup
    • Rate Limiting binding (IP-based) on magic-link endpoints
    • Web Analytics
  • Fault-Tolerant Eventual Consistency:

    • Idempotency-key dedupe in onInvoiceUpload — three guards (stale r2ActionTime, active workflow instance, terminal status) tolerate at-least-once R2 events
    • Schedule-first invoice delete — this.schedule(0, ...) persists the R2 cleanup intent before the local row delete, so a crash converges on the next alarm tick (1s→30s, 3 retries)
    • Dual-path membership sync — enqueue(FinalizeMembershipSync) before the Better Auth mutation (durable safety net) + stub.syncMembership() after (best-effort eager); both re-read D1 as authoritative
    • UserProvisioningWorkflow reconciles all three states of Better Auth's non-transactional org creation (org + owner member are separate writes)
    • Workflow instance dedupe by idempotency key prevents double-extraction
  • Authentication & Organizations:

    • Magic link authentication using Better Auth
    • Multi-tenant organization management with automatic organization creation via workflow
    • Role-based access control (user/admin/organization member roles)
    • Organization invitations and membership management
    • Agent WebSocket auth gated pre-upgrade (onBeforeConnect) with per-RPC membership re-check
  • Payments & Subscriptions:

    • Stripe integration with subscription processing
    • Monthly and annual pricing plans with configurable trial periods
    • Stripe Checkout and Customer Portal integration
    • Webhook handling for subscription lifecycle events
    • Subscription management (cancel, reactivate, billing portal access)
  • Effect v4 Architecture:

    • Services via ServiceMap.Service with explicit Layer.effect definitions
    • Traced functions with Effect.fn for observability
    • Type-safe error handling using Schema.TaggedErrorClass
    • Layer composition via Layer.merge for dependency injection
    • Service dependencies resolved via yield* for compile-time safety
    • Per-request runEffect built in worker.fetch and injected into TanStack Start's ServerContext; server functions pull it from context: { runEffect } and execute Effect pipelines without importing @tanstack/react-start/server (which would drag Node builtins into the client build graph)
    • Separate runtime layer per execution surface — Worker fetch, scheduled (Cron), queue, OrganizationAgent DO (DO-local SQLite via @effect/sql-sqlite-do), and each Workflow — so each entrypoint only materializes the services it needs
    • runEffect uses runPromiseExit + Cause.pretty to normalize failures into Error instances with non-empty .message, preserving server context across TanStack's ShallowErrorPlugin SSR dehydration, and re-throws TanStack redirect/notFound defects so router control flow works from inside Effect pipelines
  • Admin Panel:

    • Admin interface for user management
    • Session monitoring and administration
    • Customer and subscription oversight
  • UI/UX Components:

    • Shadcn with Base UI and TanStack Form integration
    • Theme switching (light/dark/system) with persistence
  • Testing Infrastructure:

    • Unit and integration tests using Vitest
    • End-to-end testing with Playwright
  • Email Integration:

    • Cloudflare transactional emails (coming)
    • Demo mode support for development without external email services
  • Security & Performance:

    • IP-based rate limiting for authentication endpoints
    • Server-side route protection and authorization
    • Secure session handling with database storage

Quick Start

Stripe

  • Install the Stripe CLI.
  • Go to stripe and create a sandbox for testing named tces-int
    • Remember secret key for STRIPE_SECRET_KEY environment variable.

Cloudflare

  • AI Gateway | Create Gateway
    • Gateway ID: tces-ai-gateway-int
    • Cache Responses: on
      • Automatically purged cached requests after 1 Week
    • Rate Limit Requests: only: on
      • Limit requests when rate exceeds 25 requests over a 60 sliding period.
    • Authenticated Gateway: on
      • Create authentication token: tces-ai-gateway-token-int
      • Remember for AI_GATEWAY_TOKEN environment variable.
  • Workers AI | REST API | Create a Workers AI API Token: tces-workers-ai-api-token-int
    • Remember for WORKERS_AI_API_TOKEN environment variable.

Google AI Studio (free api key)

  • aistudio.google.com | Get API Key | Create API key
    • Remember for GOOGLE_AI_STUDIO_API_KEY environment variable.

Local Env

  • Copy .env.example to .env.
  • Edit the keys.
  • Set STRIPE_WEBHOOK_SECRET later after you run pnpm stripe:listen below.
  • Edit account_id and CF_ACCOUNT_ID (local and production) in wrangler.jsonc
pnpm i
pnpm d1:reset
stripe login --project-name=tces-int
pnpm stripe:listen
# copy webhook signing secret to STRIPE_WEBHOOK_SECRET in .env
pnpm dev

# cron
curl "http://localhost:3000/cdn-cgi/handler/scheduled?cron=0%200%20*%20*%20*"

Testing

Stripe Test Card Details

  • Card Number: 4242 4242 4242 4242
  • Expiration: Any future date
  • CVC: Any 3-digit number

Unit and Integration Tests

pnpm test

E2E Tests

pnpm dev
pnpm stripe:listen
pnpm test:e2e

Deploy

  • Create stripe webhook
    • Endpoint URL: https://[DOMAIN]/api/auth/stripe/webhook
    • Events: checkout.session.completed, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted
  • Cloudflare Web Analytics | Add a site
    • Remember token from script for ANALYTICS_TOKEN secret below.
  • pnpm exec wrangler queues create tces-q-production
  • pnpm exec wrangler kv namespace create tces-kv-production
  • Update wrangler.jsonc production kv_namespaces
  • pnpm d1:reset:PRODUCTION
  • pnpm deploy:PRODUCTION
  • Storage & databases | R2 Object Storage | Overview | API Tokens Manage | Create User API token
    • Token name: tces-r2-user-token-production
    • Permissions: Object Read only
    • Specify bucket(s) | Apply to specific buckets only: tces-r2-production
    • Remember keys for R2_S3_ACCESS_KEY_ID and R2_S3_SECRET_ACCESS_KEY secrrets.
  • pnpm exec wrangler r2 bucket notification create tces-r2-production --event-type object-create --queue tces-q-production
  • AI Gateway | Create Gateway
    • Gateway ID: tces-ai-gateway-production
    • Cache Responses: on
      • Automatically purged cached requests after 1 Week
    • Rate Limit Requests: only: on
      • Limit requests when rate exceeds 25 requests over a 60 sliding period.
    • Authenticated Gateway: on
      • Create authentication token: tces-ai-gateway-token-production
      • Remember for AI_GATEWAY_TOKEN secret.
  • Workers AI | REST API | Create a Workers AI API Token: tces-workers-ai-api-token-production
    • Remember for WORKERS_AI_API_TOKEN secret.
  • pnpm exec wrangler secret put SECRET --env production
    • BETTER_AUTH_SECRET, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, ANALYTICS_TOKEN,R2_S3_ACCESS_KEY_ID, R2_S3_SECRET_ACCESS_KEY, AI_GATEWAY_TOKEN, WORKERS_AI_API_TOKEN GOOGLE_AI_STUDIO_API_KEY
  • Workers & Pages Settings: tces
    • Git repository: connect to git repo
    • Build configuration
      • Build command: CLOUDFLARE_ENV=production pnpm build
      • Deploy command: pnpm exec wrangler deploy --env production
  • Storage & databases: tces-d1-production: Settings
    • Enable read replication

Shadcn with Base UI

pnpm dlx shadcn@latest add --overwrite accordion alert-dialog alert aspect-ratio avatar badge breadcrumb button-group button calendar card carousel chart checkbox collapsible combobox command context-menu dialog drawer dropdown-menu empty field hover-card input-group input item label pagination popover progress radio-group scroll-area select separator sidebar sonner spinner switch tabs table textarea toggle tooltip

pnpm dlx shadcn@latest add https://ai-sdk.dev/elements/api/registry/all.json

Llms

pnpm add -g @playwright/cli@latest

Credit

Homepage / Pricing design by dev-xo. See his remix-saas for a production-ready saas template for remix.

License

Licensed under the MIT License.

About

Lightweight saas template packed with essential functionality for TanStack Start, Cloudflare, and Effect

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors