{"id":46631687,"url":"https://github.com/contember/lopata","last_synced_at":"2026-03-08T00:09:57.731Z","repository":{"id":339632339,"uuid":"1161989733","full_name":"contember/lopata","owner":"contember","description":"Drop-in replacement for wrangler dev and @cloudflare/vite-plugin, powered by Bun.","archived":false,"fork":false,"pushed_at":"2026-03-02T17:31:21.000Z","size":1351,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-02T20:57:10.264Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/contember.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-19T18:45:24.000Z","updated_at":"2026-03-02T17:31:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/contember/lopata","commit_stats":null,"previous_names":["contember/lopata"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/contember/lopata","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contember%2Flopata","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contember%2Flopata/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contember%2Flopata/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contember%2Flopata/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/contember","download_url":"https://codeload.github.com/contember/lopata/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contember%2Flopata/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30229591,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T19:01:10.287Z","status":"ssl_error","status_checked_at":"2026-03-07T18:59:58.103Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-08T00:09:57.196Z","updated_at":"2026-03-08T00:09:57.726Z","avatar_url":"https://github.com/contember.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lopata 🪏\n\n\u003e **This is a local development tool only.** Lopata is not intended for production use and never will be. Always deploy to Cloudflare Workers for production workloads.\n\n\u003e **Alpha software.** Lopata is in early development. APIs and behavior may change. We'd love for you to try it out and [report any issues](https://github.com/contember/lopata/issues) you find.\n\nDrop-in replacement for `wrangler dev` and `@cloudflare/vite-plugin`, powered by Bun. Run your Cloudflare Worker code locally with real bindings — no workerd, no miniflare.\n\n## Why Lopata?\n\nThe official `wrangler dev` uses workerd (the Cloudflare Workers runtime) under the hood via miniflare. While this gives you high-fidelity emulation, it comes with trade-offs that hurt the daily development experience:\n\n- **Slow startup and reload** — workerd is a separate process that needs to spin up an isolate, bundle your code, and establish IPC. Every code change goes through this cycle, adding noticeable latency to the feedback loop.\n- **Difficult debugging** — because your code runs inside workerd (not your local JS runtime), setting breakpoints, inspecting variables, and stepping through code requires a remote debugging protocol. Stack traces from binding errors are often opaque.\n- **Vite integration friction** — `@cloudflare/vite-plugin` bridges two worlds (Vite's Node/Bun process and workerd), leading to subtle issues with HMR, module resolution, and error reporting.\n- **Heavy resource usage** — running workerd alongside your dev toolchain consumes significant memory and CPU, especially with Durable Objects or multiple workers.\n\nLopata takes a different approach: it implements all Cloudflare bindings natively in TypeScript and runs your worker code directly in Bun. Everything is in-process — bindings are backed by SQLite and the local filesystem, Durable Objects share the same memory, and there's no IPC overhead. The result is near-instant startup, fast hot-reload, easy debugging with normal breakpoints, and a built-in dashboard with real-time request tracing.\n\n**The trade-off:** Lopata runs on Bun, not workerd. While the binding APIs are faithfully reimplemented (~90–95% coverage), the underlying runtime differs. Edge cases in V8 isolate semantics, request/response body handling, or undocumented Cloudflare behavior may not match exactly. For non-trivial logic, always verify your code against the official runtime (`wrangler dev` or a staging deployment) before shipping to production.\n\n## Features\n\n- **All major bindings** — KV, R2, D1, Durable Objects (with SQL API and WebSocket Hibernation), Workflows, Queues, Cache, Service Bindings, Static Assets, Images, Hyperdrive, Analytics Engine, Browser Rendering, Containers, Scheduled (Cron), Email\n- **Persistent local state** — data lives in `.lopata/` (SQLite + filesystem), survives restarts\n- **Dashboard** — browse bindings, inspect data, view request traces in real-time\n- **Vite plugin** — integrates with React Router and other Vite-based frameworks\n- **Multi-worker support** — run multiple workers with service bindings between them\n- **Hot-reload** — file changes trigger instant reload with zero downtime\n- **Request tracing** — hierarchical spans for every request, binding call, and outbound fetch\n- **Cloudflare module shims** — `cloudflare:workers`, `cloudflare:workflows`, `@cloudflare/containers`, `@cloudflare/puppeteer` all work out of the box\n- **Global API compatibility** — `caches`, `HTMLRewriter`, `WebSocketPair`, `scheduler.wait()`, `crypto.timingSafeEqual`, and more\n\n## Requirements\n\n- [Bun](https://bun.sh) v1.1+\n\n## Quick start\n\n### Standalone dev server\n\n```bash\nbun add -d lopata\nbunx lopata dev\n```\n\nThis starts a local server on `http://localhost:8787` with all bindings from your `wrangler.toml` / `wrangler.jsonc` ready to use. The dashboard is available at `http://localhost:8787/__dashboard`.\n\n### Vite plugin\n\n```bash\nbun add -d lopata\n```\n\n```ts\n// vite.config.ts\nimport { lopata } from 'lopata/vite-plugin'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n\tplugins: [\n\t\tlopata(),\n\t\t// ... your framework plugin (e.g. reactRouter())\n\t],\n})\n```\n\nThen start your dev server with Bun:\n\n```bash\nbun --bun vite dev\n# or for React Router:\nbun --bun react-router dev\n```\n\n\u003e **Note:** `bun --bun` is required to override Node.js shebangs so Bun handles `bun:sqlite` and `.ts` imports natively.\n\n## CLI\n\n```\nlopata \u003ccommand\u003e [options]\n\nCommands:\n  dev                              Start local dev server\n\n  d1 list                          List D1 databases\n  d1 execute \u003cdb\u003e --command \u003csql\u003e  Execute SQL on a D1 database\n  d1 migrations apply [db]         Apply D1 migrations\n\n  r2 object list [bucket/prefix]   List R2 objects\n  r2 object get \u003cbucket/key\u003e       Get an R2 object\n  r2 object put \u003cbucket/key\u003e -f    Upload a file to R2\n  r2 object delete \u003cbucket/key\u003e    Delete an R2 object\n\n  kv key list                      List KV keys\n  kv key get \u003ckey\u003e                 Get a KV value\n  kv key put \u003ckey\u003e \u003cvalue\u003e         Put a KV value\n  kv key delete \u003ckey\u003e              Delete a KV key\n\n  queues list                      List queues\n  queues message list \u003cqueue\u003e      List queue messages\n  queues message send \u003cqueue\u003e      Send a message\n  queues message purge \u003cqueue\u003e     Purge queue messages\n\n  cache list                       List cache names\n  cache purge [--name \u003ccache\u003e]     Purge cache entries\n\n  trace list [--limit N]           List recent traces\n  trace get \u003ctraceId\u003e              Get trace detail\n\nGlobal flags:\n  --config, -c \u003cpath\u003e   Path to wrangler config file\n  --env, -e \u003cname\u003e      Environment name\n  --help, -h            Show help\n```\n\n### Dev server flags\n\n```bash\nlopata dev [--port 8787] [--listen localhost] [--env production]\n```\n\n### Special dev endpoints\n\nThe dev server exposes endpoints for triggering handlers manually:\n\n```bash\n# Trigger a scheduled handler\ncurl \"http://localhost:8787/cdn-cgi/handler/scheduled?cron=*/5+*+*+*+*\"\n\n# Simulate an inbound email\ncurl -X POST \"http://localhost:8787/cdn-cgi/handler/email?from=a@b.com\u0026to=c@d.com\"\n```\n\n## Configuration\n\nLopata reads your standard Cloudflare config file, auto-detected in order: `wrangler.jsonc`, `wrangler.json`, `wrangler.toml`.\n\n```jsonc\n// wrangler.jsonc\n{\n\t\"name\": \"my-worker\",\n\t\"main\": \"src/index.ts\",\n\t\"compatibility_date\": \"2025-01-01\",\n\t\"kv_namespaces\": [\n\t\t{ \"binding\": \"KV\", \"id\": \"...\" }\n\t],\n\t\"r2_buckets\": [\n\t\t{ \"binding\": \"R2\", \"bucket_name\": \"my-bucket\" }\n\t],\n\t\"d1_databases\": [\n\t\t{ \"binding\": \"DB\", \"database_name\": \"my-db\", \"database_id\": \"...\" }\n\t],\n\t\"durable_objects\": {\n\t\t\"bindings\": [\n\t\t\t{ \"name\": \"COUNTER\", \"class_name\": \"Counter\" }\n\t\t]\n\t},\n\t\"queues\": {\n\t\t\"producers\": [{ \"binding\": \"MY_QUEUE\", \"queue\": \"my-queue\" }],\n\t\t\"consumers\": [{ \"queue\": \"my-queue\", \"max_batch_size\": 10 }]\n\t},\n\t\"workflows\": [\n\t\t{ \"name\": \"my-workflow\", \"binding\": \"WORKFLOW\", \"class_name\": \"MyWorkflow\" }\n\t]\n}\n```\n\n### Environment variables and secrets\n\nVariables are loaded in this order (later overrides earlier):\n\n1. `[vars]` in wrangler config\n2. `.dev.vars` file (dotenv format)\n3. `.env` file (fallback if no `.dev.vars`)\n4. `.dev.vars.\u003cenvironment\u003e` for env-specific overrides\n\n## Multi-worker setup\n\nFor projects with multiple workers and service bindings, create a `lopata.config.ts`:\n\n```ts\n// lopata.config.ts\nexport default {\n\tmain: './wrangler.jsonc',\n\tworkers: [\n\t\t{ name: 'auth-worker', config: './workers/auth/wrangler.jsonc' },\n\t\t{ name: 'email-worker', config: './workers/email/wrangler.jsonc' },\n\t],\n\t// Optional settings:\n\tcron: true, // Enable real cron scheduling\n\tisolation: 'dev', // 'dev' (in-process) or 'isolated' (worker threads)\n\tbrowser: { // Browser Rendering config\n\t\theadless: true,\n\t},\n}\n```\n\nWorkers can call each other via service bindings configured in their respective `wrangler.jsonc`. Both HTTP (`binding.fetch()`) and RPC (`binding.myMethod()`) modes are supported, including promise pipelining.\n\n### Route patterns\n\nIn multi-worker setups, auxiliary workers can use Cloudflare-style `routes` in their `wrangler.jsonc` to handle specific URL patterns. The main worker acts as a fallback for unmatched requests.\n\n```jsonc\n// workers/api/wrangler.jsonc\n{\n\t\"name\": \"api-worker\",\n\t\"main\": \"src/index.ts\",\n\t\"routes\": [\n\t\t\"example.com/api/*\",\n\t\t{ \"pattern\": \"example.com/webhooks/*\" }\n\t]\n}\n```\n\nRoutes support trailing `*` wildcards (`/api/*` matches `/api/foo` and `/api/foo/bar`). When multiple routes match, the most specific one wins (more path segments \u003e fewer, exact match \u003e wildcard). The domain portion is stripped — only the path is matched locally.\n\n### Host-based routing\n\nFor routing by hostname (e.g. subdomains), use `hosts` in `lopata.config.ts`:\n\n```ts\nexport default {\n\tmain: './wrangler.jsonc',\n\tworkers: [\n\t\t{\n\t\t\tname: 'api-worker',\n\t\t\tconfig: './workers/api/wrangler.jsonc',\n\t\t\thosts: ['api.localhost', '*.api.localhost'],\n\t\t},\n\t],\n}\n```\n\nWildcard patterns like `*.localhost` match any subdomain. Requests matching a host pattern are routed to that worker regardless of path-based routes.\n\n## Vite plugin\n\nThe Vite plugin is a drop-in replacement for `@cloudflare/vite-plugin`. It provides:\n\n- Virtual module resolution (`cloudflare:workers`, `cloudflare:workflows`, `@cloudflare/containers`)\n- Global Cloudflare API injection (`caches`, `HTMLRewriter`, `WebSocketPair`, etc.)\n- Dev server middleware that intercepts requests and calls your worker's `fetch()` handler\n- Dashboard and tracing at `/__dashboard`\n- React Router integration with automatic loader/action instrumentation\n\n```ts\n// vite.config.ts\nimport { reactRouter } from '@react-router/dev/vite'\nimport { lopata } from 'lopata/vite-plugin'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n\tplugins: [\n\t\tlopata({\n\t\t\tconfigPath: './wrangler.jsonc', // Optional, auto-detected\n\t\t\tviteEnvironment: { name: 'ssr' }, // Optional, default 'ssr'\n\t\t\tauxiliaryWorkers: [ // Optional, for multi-worker\n\t\t\t\t{ configPath: './workers/auth/wrangler.jsonc' },\n\t\t\t],\n\t\t}),\n\t\treactRouter(),\n\t],\n})\n```\n\n## Dashboard\n\nThe built-in dashboard is available at `/__dashboard` and provides:\n\n- **Traces** — real-time request waterfall with filtering by path, status, and attributes\n- **KV** — browse namespaces, keys, and values\n- **R2** — browse buckets and objects\n- **D1** — SQL browser for all D1 databases\n- **Durable Objects** — inspect instances and their storage\n- **Queues** — browse and manage queue messages\n- **Workflows** — view workflow instances, status, and step history\n- **Cache** — browse cache entries\n- **Analytics Engine** — browse data points\n- **Email** — view captured outbound emails\n- **Scheduled** — manually trigger cron handlers\n- **AI** — browse AI binding request logs\n- **Containers** — manage running containers\n\n## Supported bindings\n\n| Binding               | Storage                      | Coverage    |\n| --------------------- | ---------------------------- | ----------- |\n| **KV**                | SQLite                       | 100%        |\n| **R2**                | Filesystem (`.lopata/r2/`)   | ~95%        |\n| **D1**                | SQLite files (`.lopata/d1/`) | ~90%        |\n| **Durable Objects**   | SQLite (KV + SQL API)        | ~90%        |\n| **Workflows**         | SQLite                       | 100%        |\n| **Queues**            | SQLite                       | ~90%        |\n| **Cache API**         | SQLite                       | 100%        |\n| **Static Assets**     | Filesystem                   | ~90%        |\n| **Service Bindings**  | In-process                   | ~85%        |\n| **Scheduled (Cron)**  | In-memory timer              | 100%        |\n| **Images**            | Sharp                        | ~80%        |\n| **Hyperdrive**        | TCP via `Bun.connect()`      | Passthrough |\n| **Workers AI**        | Proxies to Cloudflare API    | Passthrough |\n| **Analytics Engine**  | SQLite                       | Full        |\n| **Browser Rendering** | Local Puppeteer              | Full        |\n| **Containers**        | Docker                       | Full        |\n| **Send Email**        | SQLite (captured)            | Full        |\n\nOverall compatibility: **~90–95%** of the Cloudflare Workers API surface.\n\n## Local data\n\nAll persistent state is stored in `.lopata/` in your project directory:\n\n```\n.lopata/\n  data.sqlite     # KV, DO, Workflows, Queues, Cache, Analytics, AI logs, Email\n  r2/             # R2 object storage\n  d1/             # D1 databases (one .db file per database)\n```\n\nAdd `.lopata/` to your `.gitignore`.\n\n## How it differs from Wrangler\n\n|                      | Lopata                                     | Wrangler (`wrangler dev`)         |\n| -------------------- | ------------------------------------------ | --------------------------------- |\n| **Runtime**          | Bun                                        | workerd (via miniflare)           |\n| **Bindings**         | Native TypeScript implementations          | workerd built-in                  |\n| **Durable Objects**  | In-process (shared memory, easy debugging) | Isolated (faithful to production) |\n| **Module shims**     | `Bun.plugin()` virtual modules             | workerd native modules            |\n| **Dashboard**        | Built-in with real-time tracing            | Separate (Cloudflare dashboard)   |\n| **Vite integration** | Drop-in plugin                             | `@cloudflare/vite-plugin`         |\n| **Config**           | Reads `wrangler.toml`/`.jsonc`/`.json`     | Same                              |\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontember%2Flopata","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcontember%2Flopata","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontember%2Flopata/lists"}