217 lines
7.4 KiB
Markdown
217 lines
7.4 KiB
Markdown
# 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.
|