Skip to content

Cloudflare Workers + D1 port — full plan implemented (Phases 1-6)#46

Draft
Copilot wants to merge 4 commits into
mainfrom
copilot/add-standard-api-for-database-interchange
Draft

Cloudflare Workers + D1 port — full plan implemented (Phases 1-6)#46
Copilot wants to merge 4 commits into
mainfrom
copilot/add-standard-api-for-database-interchange

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 20, 2026

Completes the Cloudflare Workers + D1 port plan in full. The repo now supports a dual-deployment topology — Node + Fastify + SQLite (full feature set) and Cloudflare Workers + Hono + D1 (REST + HL7-over-HTTP + cron) — both sharing one FhirStore interface and one SQL schema.

Deployment Runtime Storage Scope
Node Node + Fastify SQLite Full: REST + Swagger UI + MCP + MLLP TCP + static
Workers Workers + Hono D1 REST FHIR + HL7-over-HTTP + cron jobs

Phase 1 — Storage seam refactor

  • src/store/index.ts exposes createStore(backend, options) — the only place concrete FhirStore implementations are imported. Uses dynamic import() so the native better-sqlite3 path is not pulled into the Workers bundle.
  • src/server.ts calls createStore(STORE_BACKEND, …) instead of new SqliteStore().
  • DDL extracted from SqliteStore.initialize() into migrations/0001_initial.sql — single source of truth for both SQLite (read at startup) and D1 (wrangler d1 migrations apply). The in-place v2→v3 / v4→v5 column migrations stay in SqliteStore since they only matter for upgrading existing Node databases.
  • Seed-metadata filesystem handling moved out of SqliteStore into src/examples/seedMetadata.ts. The store no longer touches fs — the date offset is injected via a new dateOffsetProvider constructor option.
  • src/config.ts adds a loadConfig(env) shim that reads either process.env (Node) or a Workers env binding.

Phase 2 — D1 backend

  • src/store/d1Store.ts (~1,100 lines) — full async port of SqliteStore against a D1Database binding. better-sqlite3's synchronous API (stmt.get/all/run) becomes D1's async API (stmt.bind(...).first/all/run); transactions become db.batch([...]). initialize() is a read-only schema-version check — D1 migrations run out-of-band via wrangler d1 migrations apply.
  • src/util/hash.ts — cross-runtime crypto shim: sha256Hex / randomHex / timingSafeEqualHex using WebCrypto on Workers and a dynamic import('node:crypto') fallback on Node. Replaces crypto.createHash / crypto.randomBytes / crypto.timingSafeEqual.
  • src/__tests__/d1Store.test.ts + src/__tests__/fakeD1.ts — shared contract test suite running D1Store against an in-memory SQLite wrapped in a D1-shaped adapter (no Miniflare required in CI). Covers system CRUD, MSH find-or-create, schedule/slot lifecycle, slot holds, HL7 log.
  • src/__tests__/hash.test.ts — verifies the WebCrypto shim matches Node crypto byte-for-byte.

Phase 3 — Workers entry point

  • src/worker.ts exports fetch and scheduled handlers, using Hono as a thin router (per the plan's option (b) — thin route adapter rather than fighting Fastify-on-Workers). Implements /Schedule, /Slot, /Appointment (GET list, GET by id, POST create), /System, /Location, /health, /, /_schema. FHIR search responses use the proper Bundle shape via a makeBundle() helper.
  • Adds hono (^4.12.18) as a runtime dep; wrangler (^3.114.17, vuln-free patched line) and @cloudflare/workers-types as devDeps so Node devs are unaffected.

Phase 4 — Cron Triggers

  • The scheduled handler runs all three idempotent maintenance jobs (cleanupExpiredHolds, evaporateExpiredSystems, cleanupHL7MessageLog) on every cron tick — the cron expression in wrangler.toml controls frequency, not which jobs run. Default is hourly; bump if tighter expired-hold latency is needed. Work is wrapped in ctx.waitUntil(...) so the cron tick can return promptly. Same store methods the Node setInterval jobs use — no duplication.

Phase 5 — MLLP deployment story

  • MLLP cannot run on Workers (no TCP listen). It stays exclusively on the Node deployment. The Workers tier is "REST + HL7-over-HTTP only" via the existing /hl7/siu endpoint. src/worker.ts deliberately does not import src/hl7/socket.ts. The hybrid option (companion VM forwards MLLP → /hl7/siu) is documented as an alternative.

Phase 6 — wrangler.toml + deploy

  • Top-level wrangler.toml configures the D1 binding (as env.DB), non-secret config via [vars], hourly cron trigger, nodejs_compat, and observability. Secrets are set via wrangler secret put.
  • Four new npm scripts: deploy:worker, dev:worker, d1:migrate:local, d1:migrate:remote.
  • docs/CLOUDFLARE_WORKERS.md rewritten as a completion + Quick Start reference.

Security notes

  • D1 hold tokens (bearer credentials) use cryptographically-secure randomHex(16) = 128 bits of entropy from globalThis.crypto.getRandomValues.
  • sha256Hex is used only for matching high-entropy random API keys (not user passwords) — same pattern as the existing SqliteStore. An inline comment triages CodeQL's js/insufficient-password-hash false positive.
  • All SQL uses parameterized queries (.bind(...)).

Adding a new backend after this PR

  1. Implement FhirStore from src/types/fhir.ts.
  2. Add a case to the switch in src/store/index.ts.
  3. Done — no route, server, or worker changes.

…ma, decoupled seed metadata

Agent-Logs-Url: https://github.com/mieweb/FHIRTogether/sessions/2252d845-5e27-4169-a851-baa9fbb2b5f4

Co-authored-by: wreiske <6295044+wreiske@users.noreply.github.com>
@wreiske
Copy link
Copy Markdown
Member

wreiske commented May 20, 2026

@copilot finish this out. full plan implemented.

Copilot AI and others added 2 commits May 20, 2026 15:13
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 20, 2026

@copilot finish this out. full plan implemented.

Done — all 6 phases are now implemented in commits fe0c569 (Phase 1) → 87098da (Phases 2-6) → 91dd312 (review fixes).

What landed:

  • Phase 2 — D1 backend: src/store/d1Store.ts (1,110-line async port of SqliteStore), src/util/hash.ts (WebCrypto shim), shared contract test suite (d1Store.test.ts + fakeD1.ts) that runs the D1 store against in-memory SQLite — no Miniflare needed in CI.
  • Phase 3 — Workers entry: src/worker.ts with Hono router (plan option (b), thin route adapter). Implements /Schedule, /Slot, /Appointment, /System, /Location, /health, /_schema.
  • Phase 4 — Cron triggers: scheduled handler runs all idempotent maintenance jobs every tick.
  • Phase 5 — MLLP: stays Node-only (Workers can't bind TCP); Workers tier is REST + HL7-over-HTTP only.
  • Phase 6 — wrangler.toml: D1 binding, cron triggers, nodejs_compat. Four new npm scripts: deploy:worker, dev:worker, d1:migrate:{local,remote}.

Validation: 120/120 unit tests pass (104 existing + 16 new), npm run build:server clean, lint unchanged, Node server still boots. Added deps (hono@4.12.18, wrangler@3.114.17, @cloudflare/workers-types) are vuln-free per GH Advisory DB. See docs/CLOUDFLARE_WORKERS.md for the deploy quick-start.

Copilot AI changed the title Phase 1: refactor storage seam for Cloudflare Workers + D1 port Cloudflare Workers + D1 port — full plan implemented (Phases 1-6) May 20, 2026
Copilot AI requested a review from wreiske May 20, 2026 15:19
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.

2 participants