Cloudflare workers hono angular saas
/cloudflare-workers-hono-angular-saas-cursorrules-prompt-filecFollow the "Cursor" guidelines to develop Cloudflare Workers, Hono, Angular, and SaaS in a consistent and maintainable way.
--- description: "Cursor rules for full-stack SaaS applications on Cloudflare Workers with Hono APIs, Angular frontends, typed RPC, D1/Neon, and production observability." globs: */ alwaysApply: false --- # Cloudflare Workers + Hono + Angular SaaS Full-stack SaaS on Cloudflare Workers with Hono API, Angular frontend, and enterprise integrations. ## Stack CF Workers+Hono v4.12+ | Angular 21+Ionic 8+PrimeNG 21 | D1/Neon | Drizzle v1 | Zod | Clerk Core 3 | Stripe | Inngest v4 | Resend | Bun 1.3 | Playwright v1.59+ | Vitest | ESLint+Prettier | PostHog | Sentry ## TypeScript - Strict mode, never any (use unknown), prefer interface over type - readonly when not reassigned, undefined over null - Zod as source of truth for validation - ESLint flat config (eslint.config.ts) + typescript-eslint + Prettier ## Hono API - Inline handlers for RPC type inference (never separate controller files) - Method chaining: app.use().get().post() preserves types - hc<AppType>(BASE_URL) for typed client - @hono/zod-validator on ALL request bodies - app.onError() + app.notFound() centralized - Split large apps: app.route('/path', subApp) - Error envelope: { error: string, code?: string, details?: unknown } - createFactory<{ Bindings: Env }>() for reusable middleware chains - GET /health returns { status, version, timestamp } ## Angular - Standalone only (no NgModules), Angular 21 zoneless by default - Signals stable: signal(), computed(), effect(), linkedSignal(), resource() - HttpResource for data fetching - Control flow: @if/@for/@switch/@defer (not *ngIf/*ngFor) - kebab-case files, one component per file, providedIn: 'root' - PrimeNG for UI components ## Drizzle v1 - sqliteTable for D1, plural snake_case tables - $inferSelect/$inferInsert for types - createInsertSchema/createSelectSchema from drizzle-orm/zod - Batch API (not BEGIN : D1 doesn't support transactions) - Prepared statements for repeated queries ## CF Workers - CPU limit: 10ms free / 30s paid - ctx.waitUntil() for async post-response work - ctx.passThroughOnException() for graceful degradation - Bindings typed via Env interface - D1 global read replication for latency reduction - Workers Builds for native CI/CD (preview URLs per branch) ## Inngest v4 (Background Jobs) - eventType('name', { schema: z.object({...}) }) per-event (v4 breaking) - inngest/cloudflare adapter + inngest.setEnvVars(c.env) for Workers - Step functions: step.run(), step.sleep(), step.waitForEvent(), step.sendEvent() - step.ai.infer() offloads inference (zero compute during wait) - step.realtime.publish() for durable pub/sub - Each step idempotent, retried independently ## Testing (TDD) - Failing test FIRST, then implement - Playwright for E2E: 6 breakpoints (375, 390, 768, 1024, 1280, 1920) - Vitest for unit tests - No sleeps : use waitFor/toBeVisible() - Selectors: data-testid > role > text - axe-core 0 violations - PROD_URL env var for production testing ## Security (OWASP Top 10:2025) - Must: HSTS, CSP (nonce-based strict), X-Content-Type-Options, X-Frame-Options - Must: Referrer-Policy, Permissions-Policy, COOP, COEP, CORP - Remove: X-XSS-Protection, Expect-CT, Server, X-Powered-By - Turnstile on all forms, Zod validation on all inputs - Stripe webhooks: verify signature, deduplicate via KV ## Auth (Clerk) - JWT verified per-request (no session store) - Webhook sync: Clerk → D1 for user data - RBAC: Clerk org roles for org-scoped, D1 for app-level - Route layers: public → auth-only → role-gated → owner-only ## Quality - Lighthouse: a11y ≥95, perf ≥75 - WCAG 2.2 AA compliance - LCP ≤2.5s, CLS ≤0.1, INP ≤200ms - JS ≤200KB gz, CSS ≤50KB gz - Functions ≤50 lines, cyclomatic complexity ≤10 ## Deploy ``bash npx wrangler deploy && curl -sX POST \ "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \ -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"purge_everything":true}' ` ## Hono Worker Starter `typescript import { Hono } from 'hono'; import { secureHeaders } from 'hono/secure-headers'; import { cors } from 'hono/cors'; interface Env { DB: D1Database; KV: KVNamespace; AI: Ai; TURNSTILE_SECRET: string; } const app = new Hono<{ Bindings: Env }>(); app.use('*', secureHeaders()); app.use('/api/*', cors({ origin: ['https://yourdomain.com'] })); app.get('/health', (c) => c.json({ status: 'ok', timestamp: new Date().toISOString() })); export default app; ``