feat: add cloudflare workers deployment
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,216 @@
|
||||
# Cloudflare Workers Deployment Design
|
||||
|
||||
## Goal
|
||||
|
||||
Convert the GEO Agent Article Optimizer into a Cloudflare Workers deployable app
|
||||
using the official Cloudflare bindings path for persistence and private exports.
|
||||
The deployed app must be a real production-shaped target, not only a static or
|
||||
partially working preview.
|
||||
|
||||
## Current State
|
||||
|
||||
The app is a Next.js application with API routes. It currently stores data in a
|
||||
local SQLite file through `better-sqlite3` and writes exports under
|
||||
`data/exports/<jobId>/`. That is suitable for local MVP use, but it does not map
|
||||
to Cloudflare Workers because production Workers should not depend on a mutable
|
||||
local filesystem or native SQLite addons.
|
||||
|
||||
## Chosen Approach
|
||||
|
||||
Use Next.js on Cloudflare Workers through the OpenNext Cloudflare adapter and
|
||||
Wrangler. Cloudflare production persistence will use:
|
||||
|
||||
- D1 binding `DB` for relational application data.
|
||||
- Private R2 binding `EXPORT_BUCKET` for generated export objects.
|
||||
|
||||
The app will keep local Node development separate from Cloudflare deployment:
|
||||
|
||||
- `npm run dev` remains the local developer path.
|
||||
- Local Node tests can continue using local SQLite and filesystem exports.
|
||||
- Cloudflare preview/deploy scripts use OpenNext and Wrangler.
|
||||
- Production code paths access D1 and R2 only through bindings, not REST APIs.
|
||||
|
||||
## Environment Separation
|
||||
|
||||
Local and online environments must be explicit.
|
||||
|
||||
- Local development uses `.env.local`, local SQLite, and local export files.
|
||||
- Cloudflare preview uses Wrangler local bindings and Cloudflare-compatible
|
||||
runtime behavior.
|
||||
- Staging and production each get their own D1 database, R2 bucket, Worker name,
|
||||
and secrets.
|
||||
- Deployments are manual. No Git auto-deploy or automatic hot update workflow is
|
||||
configured.
|
||||
|
||||
Recommended script split:
|
||||
|
||||
- `dev`: local Next.js development.
|
||||
- `build`: regular Next.js build for local validation.
|
||||
- `build:worker`: OpenNext Cloudflare build.
|
||||
- `preview:worker`: Wrangler local preview.
|
||||
- `deploy:worker:staging`: explicit staging deploy.
|
||||
- `deploy:worker:production`: explicit production deploy.
|
||||
|
||||
## API Access Control
|
||||
|
||||
All `/api/*` routes are protected by default with an API access key.
|
||||
|
||||
- The expected secret is `API_ACCESS_KEY`.
|
||||
- Clients send the key in `x-api-key`.
|
||||
- Local development and tests may use an explicit test/dev bypass or a local
|
||||
`.env.local` value.
|
||||
- Production secrets are set through Wrangler secrets, not committed files.
|
||||
- Missing or incorrect keys return `401`.
|
||||
|
||||
The first implementation can use a single shared key because the current app has
|
||||
no account model. The design should leave room for a future account/session
|
||||
layer without mixing that concern into the Cloudflare migration.
|
||||
|
||||
## Data Storage
|
||||
|
||||
### Repository Boundary
|
||||
|
||||
API routes should not call `better-sqlite3` directly. Introduce a repository
|
||||
boundary for article jobs, brand templates, fact cards, optimized articles, and
|
||||
QA reports.
|
||||
|
||||
Implementations:
|
||||
|
||||
- Local Node implementation: uses existing SQLite behavior for tests and local
|
||||
development.
|
||||
- Cloudflare implementation: uses D1 binding `DB` and async queries.
|
||||
|
||||
The route layer chooses the implementation based on runtime context. Online
|
||||
Workers must fail clearly if required bindings are missing instead of silently
|
||||
falling back to local storage.
|
||||
|
||||
### D1 Schema Management
|
||||
|
||||
D1 schema is managed only through migrations.
|
||||
|
||||
- Runtime code must not automatically create, rebuild, drop, or clear tables.
|
||||
- Initial schema lives in a D1 migration file.
|
||||
- Future schema changes use compatibility-first migrations, such as
|
||||
`ALTER TABLE ... ADD COLUMN` when possible.
|
||||
- If a change requires reshaping existing data, add a dedicated data migration
|
||||
script or migration step with clear staging validation instructions.
|
||||
- Before production deployment, migrations are applied and verified against
|
||||
local/staging D1 first, then applied to production.
|
||||
|
||||
This replaces the current local `initializeSchema` runtime behavior for the
|
||||
Cloudflare path. Local test setup may still initialize local SQLite test
|
||||
databases because those are disposable test fixtures, not deployed production
|
||||
state.
|
||||
|
||||
## Export Storage
|
||||
|
||||
Split export handling into rendering and storage.
|
||||
|
||||
Rendering remains runtime-agnostic:
|
||||
|
||||
- Markdown renderer.
|
||||
- QA JSON renderer.
|
||||
- DOCX renderer.
|
||||
|
||||
Storage becomes environment-specific:
|
||||
|
||||
- Local storage writes to `data/exports/<jobId>/`.
|
||||
- Cloudflare storage writes objects to private R2 through `EXPORT_BUCKET`.
|
||||
|
||||
R2 object keys:
|
||||
|
||||
- `exports/<jobId>/optimized.md`
|
||||
- `exports/<jobId>/optimized.docx`
|
||||
- `exports/<jobId>/qa_report.json`
|
||||
|
||||
The R2 bucket is private. Do not configure a public bucket URL for app exports.
|
||||
Downloads go through the authenticated Worker API, which reads the object from
|
||||
R2 and returns it as an attachment.
|
||||
|
||||
## Request Flow
|
||||
|
||||
1. `POST /api/jobs` checks `x-api-key`, normalizes input, writes the job to the
|
||||
active repository, and returns the candidate fact card.
|
||||
2. `POST /api/jobs/:jobId/confirm-fact-card` checks the key, loads the job,
|
||||
writes the brand template and confirmed fact card, and updates job status.
|
||||
3. `POST /api/jobs/:jobId/optimize` checks the key, loads job and fact card,
|
||||
runs the optimization workflow, saves article and QA report, and writes
|
||||
export objects if QA does not fail.
|
||||
4. `GET /api/jobs/:jobId/exports/:fileName` checks the key and returns the
|
||||
object from local storage or private R2.
|
||||
|
||||
## Wrangler And Bindings
|
||||
|
||||
Use `wrangler.jsonc`.
|
||||
|
||||
Required production bindings:
|
||||
|
||||
- D1 database binding `DB`.
|
||||
- R2 bucket binding `EXPORT_BUCKET`.
|
||||
|
||||
Required secrets:
|
||||
|
||||
- `API_ACCESS_KEY`.
|
||||
- LLM provider secrets such as `DEEPSEEK_API_KEY` or `OPENAI_API_KEY`.
|
||||
|
||||
Recommended non-secret vars:
|
||||
|
||||
- `LLM_PROVIDER`.
|
||||
- `DEEPSEEK_BASE_URL`.
|
||||
- `DEEPSEEK_MODEL`.
|
||||
- `DEEPSEEK_THINKING`.
|
||||
- `OPENAI_MODEL`.
|
||||
- `APP_RUNTIME=cloudflare` for clarity if needed by runtime selection.
|
||||
|
||||
Enable `nodejs_compat` because the app and dependencies use Node-compatible
|
||||
packages, including document generation and the OpenAI client.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Missing API key: `401`.
|
||||
- Missing Cloudflare binding in Worker runtime: `500` with a concise operational
|
||||
error, without exposing secret values.
|
||||
- Missing job or export object: `404`.
|
||||
- Fact card not confirmed before optimization: `409`.
|
||||
- D1/R2 operation failures: structured server errors and enough logging context
|
||||
to identify the operation and job id.
|
||||
|
||||
## Testing And Verification
|
||||
|
||||
Tests should cover both local behavior and Cloudflare-specific behavior.
|
||||
|
||||
Local tests:
|
||||
|
||||
- Existing repository tests continue to cover SQLite behavior.
|
||||
- Export renderer tests continue to cover Markdown, JSON, and DOCX output.
|
||||
- API tests cover access-key enforcement.
|
||||
|
||||
Cloudflare-focused tests:
|
||||
|
||||
- D1 repository tests using local D1/Miniflare-compatible bindings or Wrangler.
|
||||
- R2 export store tests for write, read, missing object, and content type.
|
||||
- Runtime selection tests proving Cloudflare path uses bindings and does not
|
||||
fall back to local files.
|
||||
|
||||
Verification commands:
|
||||
|
||||
- `npm test`
|
||||
- `npm run build`
|
||||
- `npm run build:worker`
|
||||
- `npx wrangler deploy --dry-run`
|
||||
|
||||
Migration verification before production:
|
||||
|
||||
1. Apply D1 migrations locally.
|
||||
2. Apply and verify against staging D1.
|
||||
3. Deploy staging Worker and smoke test protected API calls and exports.
|
||||
4. Apply migrations to production D1.
|
||||
5. Deploy production Worker explicitly.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- User accounts and per-user permissions.
|
||||
- Public R2 asset hosting.
|
||||
- Git-triggered auto-deploy.
|
||||
- Replacing the existing LLM provider abstraction.
|
||||
- Batch queues or background workflow orchestration.
|
||||
Reference in New Issue
Block a user