-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Add Cloudflare Workers + Hono + Angular SaaS rules #266
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| # 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 | ||
|
Comment on lines
+66
to
+69
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In Hono (v4.12+), the default configuration of secureHeaders does not provide any Content-Security-Policy (CSP) header, let alone a strict nonce-based one suitable for Trusted Types enforcement. CSP must be configured explicitly via the contentSecurityPolicy option. While the middleware supports nonce-based CSP (using the NONCE helper in directives like scriptSrc) and Trusted Types directives (trustedTypes and requireTrustedTypesFor), these require explicit configuration. The default SecureHeadersOptions sets other headers like Strict-Transport-Security, X-Frame-Options, etc., but CSP is "No Setting" by default. Citations:
🏁 Script executed: cat -n "rules/cloudflare-workers-hono-angular-saas-cursorrules-prompt-file/.cursorrules" | sed -n '60,120p'Repository: PatrickJS/awesome-cursorrules Length of output: 2104 🏁 Script executed: # Search for CSP or Content-Security-Policy configuration in the codebase
rg -i "content-security-policy|csp|nonce" --type-listRepository: PatrickJS/awesome-cursorrules Length of output: 462 🏁 Script executed: # Look for secureHeaders usage and any CSP configuration
rg -i "secureHeaders|contentSecurityPolicy" -A 5 -B 2Repository: PatrickJS/awesome-cursorrules Length of output: 55 The starter code must include explicit CSP configuration to match the documented security requirements. The rules require nonce-based strict CSP as mandatory (line 66), but the starter code (lines 108–110) uses only 🤖 Prompt for AI Agents |
||
| - 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; | ||
| ``` | ||
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.
Health contract is inconsistent with the starter implementation.
Line 23 mandates
{ status, version, timestamp }, but Line 110 omitsversion. Please align the starter response with the stated API contract.Proposed fix
Also applies to: 110-110
🤖 Prompt for AI Agents