Skip to content

feat(api): run apps/api on Cloudflare Workers#25

Open
Makisuo wants to merge 4 commits intomainfrom
cf-migration-api
Open

feat(api): run apps/api on Cloudflare Workers#25
Makisuo wants to merge 4 commits intomainfrom
cf-migration-api

Conversation

@Makisuo
Copy link
Copy Markdown
Owner

@Makisuo Makisuo commented Apr 14, 2026

Summary

Moves apps/api from a Bun HTTP server to a Cloudflare Worker so it can
be deployed independently, without waiting on the in-progress
alchemy-effect infra rewrite on cf-migration.

  • Refactors apps/api into a Workers fetch handler (src/worker.ts +
    shared src/app.ts), with DatabaseD1Live using the D1 binding and a
    new EdgeCacheService backed by Workers caches.default (memory
    fallback for tests/Node).
  • Splits DatabaseLive into an abstract Database Context.Service with
    two live implementations: DatabaseLibsqlLive (tests, apps/alerting)
    and DatabaseD1Live (worker).
  • Wires drizzle D1 migrations natively: wrangler.jsonc sets
    migrations_dir: "../../packages/db/drizzle" and alchemy.run.ts
    passes migrationsDir on the D1Database resource. Drops the one-shot
    SQL bundle approach (couldn't re-run incrementally).
  • Adds apps/api/alchemy.run.ts (upstream alchemy package, mirroring
    apps/chat-agent) with stage-based naming and domain mapping, plus
    deploy:stack / destroy:stack scripts.

What this PR does not include

Everything below stays on cf-migration until it's ready:

  • .context/alchemy-effect subtree bump
  • apps/{web,landing,chat-agent,alerting}/alchemy.run.ts updates
  • packages/infra/src/railway rewrite

Notes

  • apps/api/src/services/WorkerEnvironment.ts is a local Effect Context
    tag replacing what was originally imported from
    alchemy/Cloudflare/Workers. With customConditions: ["bun"], tsc
    resolves alchemy/* to raw TS source, which fails typecheck against
    the workspace's effect beta version. Keeping a local tag means src/**
    has zero alchemy imports and the deploy-time alchemy.run.ts (outside
    src/**) doesn't pollute the tsc graph.
  • First-time deploy steps for the reviewer:
    1. bun install
    2. bun --filter=@maple/api deploy:stack (alchemy provisions D1,
      applies drizzle migrations via migrationsDir, deploys Worker)
    3. Set secrets via env vars before running: TINYBIRD_TOKEN,
      MAPLE_INGEST_KEY_ENCRYPTION_KEY,
      MAPLE_INGEST_KEY_LOOKUP_HMAC_KEY, etc. (see alchemy.run.ts for
      the full list)

Test plan

  • bun --filter=@maple/api typecheck
  • bun --filter=@maple/alerting typecheck
  • bun --filter=@maple/db typecheck
  • cd apps/api && bun test — 160/160 passing
  • wrangler d1 migrations apply MAPLE_DB --local applies all 16
    drizzle migrations; re-run is a no-op
  • Smoke test bun run dev (wrangler dev + local D1)
  • Staging deploy via bun run deploy:stack
  • Hit /health on the deployed Worker and verify one authenticated
    route against D1

🤖 Generated with Claude Code

Makisuo and others added 2 commits April 15, 2026 00:57
Refactors apps/api from a Bun HTTP server into a Workers fetch handler
so we can deploy via `wrangler deploy` without waiting on the
alchemy-effect infra rewrite.

- split DatabaseLive into an abstract Context.Service with D1 and libsql
  live layers; tests and apps/alerting use the libsql variant
- add EdgeCacheService backed by Workers caches.default with an in-memory
  fallback for Node/test runtimes; QueryEngineService uses it in place of
  Effect.Cache
- add packages/db/client factories, D1 migration bundle, and the
  scripts/prepare-local-d1.ts dev helper
- local WorkerEnvironment Context tag so we don't pull alchemy into the
  tsc graph (alchemy 2.0.0-beta.3 fails type-check against workspace
  effect 4.0.0-beta.48)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the one-shot SQL bundle with drizzle's native migration folder,
which both wrangler and alchemy read directly. The bundle approach
couldn't run incrementally — it would fail on the second apply because
tables already existed.

- wrangler.jsonc: add migrations_dir pointing at packages/db/drizzle so
  `wrangler d1 migrations apply` tracks applied migrations in the
  standard d1_migrations table
- alchemy.run.ts: declare the D1Database with migrationsDir and the
  Worker with D1 binding + env/secret wiring, mirroring the pattern in
  apps/chat-agent/alchemy.run.ts; domains per stage (api.maple.dev /
  api-staging.maple.dev)
- drop the bundle scripts (create-d1-migration-bundle, export-app-state,
  render-d1-import-sql) and the .generated SQL artifact
- rewrite scripts/prepare-local-d1.ts to delegate to
  `wrangler d1 migrations apply --local` — idempotent, picks up new
  migrations without wiping
- add alchemy dep (same pkg.pr.new version as web/landing/chat-agent) and
  deploy:stack / destroy:stack scripts

Verified: all 16 drizzle migrations apply cleanly via
`wrangler d1 migrations apply MAPLE_DB --local` and re-running is a
no-op. Typecheck and tests still pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pullfrog
Copy link
Copy Markdown

pullfrog bot commented Apr 14, 2026

no API key found. Pullfrog requires at least one LLM provider API key.

to fix this, add the required secret to your GitHub repository:

  1. go to: https://github.com/Makisuo/maple/settings/secrets/actions
  2. click "New repository secret"
  3. set the name to your provider's key (e.g., ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY)
  4. set the value to your API key
  5. click "Add secret"

configure your model at https://pullfrog.com/console/Makisuo/maple

for full setup instructions, see https://docs.pullfrog.com/keys

Pullfrog  | Rerun failed job ➔View workflow run | Triggered by Pullfrog | Using Claude Opus𝕏

CI deploy-prd failed with `Secret cannot be undefined` because
`alchemy.secret(optionalEnv(...))` was invoked for env vars that weren't
set in CI (MAPLE_ROOT_PASSWORD, CLERK_*, etc.). The D1 database and
migrations did apply successfully before the Worker step died.

Introduces `optionalSecret` / `optionalString` helpers that omit the
binding entirely when the env var is unset. The Worker runtime already
handles missing optional env via `Option` in `services/Env.ts`, so
omitting the binding is the right shape.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant