devflare 1.0.0-next.1 → 1.0.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/LLM.md +1280 -505
  2. package/README.md +326 -458
  3. package/package.json +1 -1
package/LLM.md CHANGED
@@ -1,253 +1,365 @@
1
1
  # Devflare
2
2
 
3
- This file is for LLMs and developers **using** the `devflare` library.
3
+ This file is for LLMs and developers **using** the published `devflare` package.
4
4
 
5
- It explains the public mental model, the intended project structure, and the practical authoring patterns that make Devflare different from using raw Wrangler + Miniflare directly.
5
+ It documents the public package contract, the actual config/runtime model, and the places where Devflare adds behavior on top of Bun, Vite, Miniflare, Wrangler, and Cloudflare.
6
6
 
7
- It does **not** document the internal repository layout or example cases. Treat it as package-facing guidance.
7
+ It does **not** document this repository’s internal implementation details, test fixtures, or example-case layout except when those details directly affect the public behavior of the package.
8
+
9
+ The most important thing to understand is that Devflare spans **multiple layers** that are easy to confuse:
10
+
11
+ 1. config-time `process.env`
12
+ 2. `devflare.config.ts`
13
+ 3. generated `wrangler.jsonc`
14
+ 4. runtime Worker `env` bindings
15
+ 5. local-only dev/test helpers
16
+
17
+ If two things have similar names but live in different layers, assume they are different until proven otherwise. The classic troublemakers are:
18
+
19
+ - `files.routes` vs top-level `routes`
20
+ - `vars` vs `secrets`
21
+ - Bun `.env` loading vs local runtime secret loading
22
+ - Devflare `env` overrides vs raw Wrangler environment sections
8
23
 
9
24
  ---
10
25
 
11
26
  ## What Devflare is
12
27
 
13
- Devflare is a **developer platform for Cloudflare Workers built on top of Miniflare, Wrangler-compatible config, and framework-aware tooling**.
28
+ Devflare is a **developer platform layer for Cloudflare Workers**.
14
29
 
15
- The short version:
30
+ Miniflare gives you a local Workers runtime. Wrangler gives you deployment configuration and deployment workflows. Vite gives you a frontend/build pipeline. Bun gives you the CLI runtime and process environment behavior.
16
31
 
17
- > Miniflare gives you a local Workers runtime. Devflare turns that runtime into a coherent developer system.
32
+ Devflare sits on top of those pieces and turns them into one coherent authoring model.
18
33
 
19
- Devflare does this by combining:
34
+ Concretely, Devflare provides:
20
35
 
21
36
  - typed config via `defineConfig()`
22
- - convention-driven file discovery
23
- - binding compilation to Wrangler-compatible config
24
- - local Miniflare orchestration
25
- - Node Worker bridging where needed
26
- - dedicated test helpers for each handler type
27
- - framework integration for Vite and SvelteKit
28
- - local development features that Miniflare alone does not provide cleanly, such as **browser rendering support**, multi-surface orchestration, and a consistent file-structured app model
37
+ - convention-first file discovery for worker surfaces
38
+ - compilation from Devflare config into Wrangler-compatible config
39
+ - local orchestration around Miniflare, Vite, and bridge-backed tooling
40
+ - typed multi-worker references via `ref()`
41
+ - test helpers for HTTP, queues, scheduled handlers, email, and tail events
42
+ - framework-aware integration for Vite and SvelteKit
43
+ - local features that are awkward or incomplete in raw Miniflare-only setups, including browser-rendering workflows, websocket-aware dev proxying, and unified test ergonomics
29
44
 
30
- If you are building Cloudflare applications and want more structure than a single `worker.ts` plus manual plumbing, Devflare is the layer that supplies that structure.
45
+ So Devflare is **not** “a different Workers runtime”.
46
+
47
+ It is a higher-level developer system that uses the Cloudflare ecosystem underneath.
31
48
 
32
49
  ---
33
50
 
34
- ## How Devflare extends Miniflare
51
+ ## The authoring model Devflare wants
35
52
 
36
- This is the most important concept to understand.
53
+ Devflare is intentionally opinionated.
37
54
 
38
- Devflare is **not** trying to replace Miniflare. It **extends** Miniflare so developers can work with a higher-level application model.
55
+ It wants you to think in **surfaces** rather than “one giant worker file that does everything”.
39
56
 
40
- ### Raw Miniflare gives you a runtime
57
+ Typical surfaces are:
41
58
 
42
- Miniflare is excellent at local execution and local emulation of many Cloudflare bindings.
59
+ - HTTP requests via `src/fetch.ts`
60
+ - queue consumers via `src/queue.ts`
61
+ - cron handlers via `src/scheduled.ts`
62
+ - incoming email via `src/email.ts`
63
+ - file-based HTTP routes via `src/routes/**`
64
+ - Durable Objects via `do.*.ts`
65
+ - WorkerEntrypoints via `ep.*.ts`
66
+ - workflows via `wf.*.ts`
67
+ - custom RPC serialization via `src/transport.ts`
43
68
 
44
- But by itself, it does not define:
69
+ This separation is one of Devflare’s biggest advantages:
45
70
 
46
- - how your project should be structured
47
- - how `fetch`, `queue`, `scheduled`, `email`, routes, DOs, and entrypoints should relate
48
- - how cross-worker references should stay typed
49
- - how tests should address each handler surface consistently
50
- - how framework output and auxiliary workers should be wired together
51
- - how to simulate browser rendering locally in a coherent workflow
71
+ - responsibility boundaries stay clear
72
+ - test helpers map naturally onto handler types
73
+ - multi-worker systems stay readable
74
+ - LLM-generated code stays composable instead of collapsing into a single blob
52
75
 
53
- ### Devflare adds the missing platform layer
76
+ When generating or reviewing Devflare code, prefer **one responsibility per file** unless there is a very strong reason not to.
54
77
 
55
- Devflare adds those higher-level capabilities on top of Miniflare.
78
+ ---
56
79
 
57
- Concretely, it provides:
80
+ ## Package entrypoints
58
81
 
59
- 1. **Convention-first file structure**
60
- - `src/fetch.ts`
61
- - `src/queue.ts`
62
- - `src/scheduled.ts`
63
- - `src/email.ts`
64
- - `src/routes/**`
65
- - `do.*.ts`
66
- - `ep.*.ts`
67
- - `wf.*.ts`
68
- - `src/transport.ts`
82
+ Use package subpaths intentionally.
69
83
 
70
- 2. **Config compilation**
71
- - Devflare compiles your config into Wrangler-compatible config rather than making you hand-maintain the low-level representation yourself.
84
+ | Import | What it is for | What it exposes in practice |
85
+ |---|---|---|
86
+ | `devflare` | Main package entrypoint | `defineConfig`, config loading/compilation helpers, `ref()`, unified `env`, CLI helpers, bridge helpers, decorators, transform helpers, test helpers re-exported for convenience, `workerName` |
87
+ | `devflare/runtime` | Worker-safe runtime utilities | middleware helpers like `sequence()`, `resolve()`, `pipe()`, validation helpers, decorators |
88
+ | `devflare/test` | Testing API | `createTestContext`, `cf`, `email`, `queue`, `scheduled`, `worker`, `tail`, mock helpers, remote skip helpers, bridge test context |
89
+ | `devflare/vite` | Vite integration | `devflarePlugin`, plugin context helpers, config helpers, Vite-side types |
90
+ | `devflare/sveltekit` | SvelteKit integration | `handle`, `createDevflarePlatform`, `createHandle`, dev/reset helpers, integration types |
91
+ | `devflare/cloudflare` | Cloudflare account/auth/usage helpers | `account` helper object, auth/account/resource inspection, usage tracking, limits/preferences helpers, Cloudflare API errors |
92
+ | `devflare/decorators` | Decorators only | `durableObject()` and related decorator utilities |
72
93
 
73
- 3. **Responsibility isolation**
74
- - Instead of one giant worker file containing every concern, Devflare encourages one surface per responsibility: fetch, queue, cron, email, routes, Durable Objects, WorkerEntrypoints, workflows, and transport.
94
+ ### Important runtime note
75
95
 
76
- 4. **Unified testing surface**
77
- - `cf.worker`
78
- - `cf.queue`
79
- - `cf.scheduled`
80
- - `cf.email`
81
- - `cf.tail`
96
+ `devflare/runtime` is intentionally **smaller** than the main package.
82
97
 
83
- 5. **Bridge-backed local development**
84
- - Devflare handles Node-side access, env access, transport decoding, and Miniflare orchestration so tests and tooling feel like one system.
98
+ Today it does **not** export the unified `env` proxy from the main `devflare` entrypoint. If you want `import { env } from 'devflare'`, import it from `devflare`, not `devflare/runtime`.
85
99
 
86
- 6. **Capabilities beyond raw Miniflare**
87
- - most notably **browser rendering support** through a local browser shim that lets browser-rendering workflows run locally in a way Miniflare does not provide directly
100
+ ### Common main-package exports
88
101
 
89
- 7. **Framework integration**
90
- - Vite and SvelteKit support with config generation, worker-aware transforms, auxiliary Durable Object workers, and websocket-aware local dev workflows
102
+ The most common exports from `devflare` are:
91
103
 
92
- So the design goal is not just “run a Worker locally”.
104
+ - `defineConfig`
105
+ - `loadConfig`
106
+ - `compileConfig`
107
+ - `stringifyConfig`
108
+ - `ref`
109
+ - `env`
110
+ - `workerName`
93
111
 
94
- The design goal is:
112
+ The main package also exports advanced tooling APIs such as CLI helpers, bridge helpers, DO transform helpers, WorkerEntrypoint transform helpers, decorators, and test helpers.
95
113
 
96
- > let developers build Cloudflare systems using coherent, isolated responsibilities while Devflare composes those responsibilities into a working local and testable whole
114
+ Two small but useful details:
97
115
 
98
- ---
116
+ - the package default export is also `defineConfig`
117
+ - `workerName` is a build-time-injected worker-name helper, with dev/test fallback behavior rather than being a dynamic Cloudflare metadata API
99
118
 
100
- ## The authoring model Devflare wants you to use
119
+ ### Deprecated and compatibility exports
101
120
 
102
- Devflare is opinionated in a useful way.
121
+ The package still exposes a few compatibility surfaces so older code keeps working.
103
122
 
104
- It wants you to think in **surfaces** rather than in “one file that does everything”.
123
+ Prefer the newer APIs in fresh code:
105
124
 
106
- ### HTTP surface
125
+ - `resolveRef` and `serviceBinding` are legacy helpers around the newer `ref()` model
126
+ - test helpers are re-exported from the main package for convenience, but `devflare/test` is the clearer import path for test code
127
+ - `createMultiWorkerContext` and `createEntrypointScript` from `devflare/test` are deprecated in favor of `createTestContext()` and the worker-entrypoint pattern
128
+ - `isRemoteModeEnabled()` is a deprecated alias; treat the remote-mode flow and `shouldSkip` helpers as the primary public story
129
+ - `testEnv` belongs to the bridge-test-context API, not to the main unified `env` proxy
107
130
 
108
- Use `src/fetch.ts` for normal request handling.
131
+ ---
109
132
 
110
- ### Queue surface
133
+ ## Config lifecycle
111
134
 
112
- Use `src/queue.ts` for queue consumers.
135
+ This is the actual config lifecycle Devflare uses.
113
136
 
114
- ### Scheduled surface
137
+ 1. You author `devflare.config.ts` with `defineConfig()`
138
+ 2. Devflare loads and validates it with the Zod-backed schema
139
+ 3. If an environment is selected, Devflare merges `config.env[environment]` into the base config
140
+ 4. Devflare compiles the resolved config into a Wrangler-compatible object
141
+ 5. Build/deploy flows write a generated `wrangler.jsonc`
142
+ 6. Vite/Wrangler/Miniflare then use the generated result in their respective phases
115
143
 
116
- Use `src/scheduled.ts` for cron triggers.
144
+ ### Config file names
117
145
 
118
- ### Email surface
146
+ `loadConfig()` looks for these filenames, in this order:
119
147
 
120
- Use `src/email.ts` for incoming email handling. Use `sendEmail` bindings in config for outgoing email.
148
+ - `devflare.config.ts`
149
+ - `devflare.config.mts`
150
+ - `devflare.config.js`
151
+ - `devflare.config.mjs`
121
152
 
122
- ### Routing surface
153
+ ### `defineConfig()` supported forms
123
154
 
124
- Use `files.routes` with a directory like `src/routes` when you want file-based HTTP routing instead of one monolithic fetch file.
155
+ `defineConfig()` accepts all of these forms:
125
156
 
126
- ### Durable Object surface
157
+ ```ts
158
+ import { defineConfig } from 'devflare'
127
159
 
128
- Use `do.*.ts` files for Durable Objects.
160
+ export default defineConfig({
161
+ name: 'my-worker'
162
+ })
163
+ ```
129
164
 
130
- ### RPC / service-entry surface
165
+ ```ts
166
+ import { defineConfig } from 'devflare'
131
167
 
132
- Use `ep.*.ts` files for named WorkerEntrypoints.
168
+ export default defineConfig(() => ({
169
+ name: process.env.WORKER_NAME ?? 'my-worker'
170
+ }))
171
+ ```
133
172
 
134
- ### Workflow surface
173
+ ```ts
174
+ import { defineConfig } from 'devflare'
175
+
176
+ export default defineConfig(async () => ({
177
+ name: 'my-worker'
178
+ }))
179
+ ```
135
180
 
136
- Use `wf.*.ts` files for Workflow classes.
181
+ It also supports a generic entrypoint type parameter:
137
182
 
138
- ### Transport surface
183
+ ```ts
184
+ import { defineConfig } from 'devflare'
139
185
 
140
- Use `src/transport.ts` when values crossing boundaries need custom serialization.
186
+ export default defineConfig<import('./env').Entrypoints>({
187
+ name: 'my-worker'
188
+ })
189
+ ```
141
190
 
142
- This is one of Devflare’s biggest advantages: it turns Cloudflare development from “single worker blob” into **separated, discoverable, testable responsibilities**.
191
+ That generic exists to improve type inference for `ref().worker('SomeEntrypoint')` after `env.d.ts` has been generated.
143
192
 
144
193
  ---
145
194
 
146
- ## Public package entry points
195
+ ## How env files, `vars`, and `secrets` actually work
147
196
 
148
- Use the package subpaths intentionally.
197
+ This is the section people most often get wrong.
149
198
 
150
- | Import | Use for |
151
- |---|---|
152
- | `devflare` | config, `ref()`, `env`, bridge/test convenience exports |
153
- | `devflare/runtime` | runtime-safe utilities for Worker code |
154
- | `devflare/test` | test context, mocks, `cf.*` helpers |
155
- | `devflare/vite` | Vite integration |
156
- | `devflare/sveltekit` | SvelteKit integration |
157
- | `devflare/cloudflare` | Cloudflare-specific helpers |
158
- | `devflare/decorators` | decorators such as `durableObject()` |
199
+ ### The short version
159
200
 
160
- ---
201
+ - `loadConfig()` itself does **not** run dotenv loading
202
+ - the Devflare CLI runs under **Bun**, and Bun **does** auto-load `.env` files into `process.env`
203
+ - `vars` are plain configuration values compiled into generated Wrangler config and exposed at runtime as normal bindings
204
+ - `secrets` are **declarations of expected runtime secrets**, not a place to store secret values
205
+ - actual secret values should come from Cloudflare secret storage or the local secret mechanisms used by Wrangler/Cloudflare tooling
206
+
207
+ ### The four sources you must distinguish
161
208
 
162
- ## Core ideas you should internalize
209
+ | Thing | Source of truth | Used when | Appears in generated `wrangler.jsonc` | Available as runtime binding | Safe for secrets |
210
+ |---|---|---|---|---|---|
211
+ | `process.env` | Bun process environment, shell env, or manually loaded env files | config evaluation time and build tooling | No, unless you copy values into config | Not automatically | Only if your own process handling is secure |
212
+ | `vars` | `devflare.config.ts` | compile time and runtime | **Yes** | **Yes** | **No** |
213
+ | `secrets` | secret declaration in `devflare.config.ts` plus actual runtime secret store | runtime | **No secret values are compiled** | **Yes**, if runtime values are provided | **Yes** |
214
+ | local runtime secret files such as `.dev.vars` or Wrangler-style local `.env` usage | local Cloudflare/Wrangler runtime layer | local runtime only | No | Yes | Yes |
215
+ | Cloudflare secret store | Cloudflare platform | deployed runtime | No | Yes | Yes |
163
216
 
164
- ### `defineConfig()` is the center of the project
217
+ ### What Devflare itself does and does not load
165
218
 
166
- Always define your worker through `defineConfig()`.
219
+ `loadConfig()` calls the underlying config loader with `dotenv: false`.
220
+
221
+ That means **Devflare’s config loader does not provide its own dotenv layer**.
222
+
223
+ However, the published CLI entrypoint runs via Bun, and Bun auto-loads env files such as:
224
+
225
+ - `.env`
226
+ - `.env.<NODE_ENV>`
227
+ - `.env.local`
228
+
229
+ So when you run the Devflare CLI normally, `process.env` is often already populated **before** `devflare.config.ts` is evaluated.
230
+
231
+ That gives you this effective behavior:
232
+
233
+ - **CLI usage under Bun**: `.env` values are usually available via `process.env`
234
+ - **programmatic `loadConfig()` in another runtime**: do **not** assume env files were loaded unless your process already loaded them
235
+
236
+ This distinction matters a lot. The env-file loading is coming from **Bun**, not from Devflare’s config loader.
237
+
238
+ ### Recommended mental model
239
+
240
+ Treat config-time env and runtime env as separate layers:
241
+
242
+ 1. read config-time values from `process.env`
243
+ 2. put **non-secret** values that should become Worker bindings in `vars`
244
+ 3. declare **secret names** in `secrets`
245
+ 4. provide actual secret values through local runtime secret files or Cloudflare secret storage
246
+
247
+ ### Example: non-secret build/runtime config vs runtime secret
167
248
 
168
249
  ```ts
169
250
  import { defineConfig } from 'devflare'
170
251
 
171
- export default defineConfig({
172
- name: 'my-worker'
173
- })
252
+ export default defineConfig(() => ({
253
+ name: 'billing-worker',
254
+ vars: {
255
+ PUBLIC_API_BASE: process.env.PUBLIC_API_BASE ?? 'http://localhost:5173',
256
+ LOG_LEVEL: process.env.LOG_LEVEL ?? 'info'
257
+ },
258
+ secrets: {
259
+ STRIPE_SECRET_KEY: { required: true },
260
+ POSTMARK_TOKEN: { required: true }
261
+ }
262
+ }))
174
263
  ```
175
264
 
176
- This is the source of truth Devflare uses to:
177
-
178
- - understand your bindings
179
- - understand your file layout
180
- - generate Wrangler-compatible config
181
- - drive dev mode
182
- - drive test mode
265
+ What this means:
183
266
 
184
- ### Convention beats boilerplate
267
+ - `PUBLIC_API_BASE` and `LOG_LEVEL` are not treated as secret and may be baked into generated config
268
+ - `STRIPE_SECRET_KEY` and `POSTMARK_TOKEN` are declared as required runtime secrets
269
+ - the **values** for those secrets should come from a runtime secret source, not from `vars`
185
270
 
186
- If your filenames follow the defaults, you do not need to spell everything out.
271
+ ### Why `vars` are not secrets
187
272
 
188
- Defaults include:
273
+ Devflare’s compiler writes `vars` into the generated Wrangler config object.
189
274
 
190
- - `src/fetch.ts`
191
- - `src/queue.ts`
192
- - `src/scheduled.ts`
193
- - `src/email.ts`
194
- - `src/transport.ts`
195
- - `**/do.*.{ts,js}`
196
- - `**/ep.*.{ts,js}`
197
- - `**/wf.*.{ts,js}`
275
+ That means they are part of the deployment configuration shape. In other words:
198
276
 
199
- ### `ref()` is how cross-worker systems stay clean
277
+ - they are configuration
278
+ - they are intentionally reproducible
279
+ - they are suitable for public base URLs, feature flags, modes, IDs, and other non-secret values
280
+ - they are **not** suitable for API keys, tokens, passwords, or signing secrets
200
281
 
201
- If one worker depends on another worker or on another worker’s Durable Objects, use `ref()` instead of manually duplicating names.
282
+ ### What `secrets` actually mean in Devflare
202
283
 
203
- That keeps cross-worker relationships typed and centralized.
284
+ The current `secrets` field is a declaration layer. It tells Devflare which secret names exist and whether they are required.
204
285
 
205
- ### `env` is the unified access point
206
-
207
- Use:
286
+ Today, each secret is configured as:
208
287
 
209
288
  ```ts
210
- import { env } from 'devflare'
289
+ secrets: {
290
+ MY_SECRET: { required: true }
291
+ }
211
292
  ```
212
293
 
213
- Devflare makes `env` work coherently across request handling, tests, and bridge-backed local flows.
294
+ What this does **not** mean:
214
295
 
215
- ---
296
+ - it does not embed the secret value in `devflare.config.ts`
297
+ - it does not compile secret values into `wrangler.jsonc`
298
+ - it does not create a second custom secret-store format on top of Cloudflare/Wrangler
216
299
 
217
- ## Why file structure matters so much in Devflare
300
+ What it **does** mean:
218
301
 
219
- In plain Worker setups, it is common to end up with a single file that mixes:
302
+ - you are declaring an expected runtime binding
303
+ - generated types can treat that binding as a string-like secret binding
304
+ - your runtime still needs the actual value from a proper secret source
220
305
 
221
- - HTTP routing
222
- - queue handling
223
- - scheduled jobs
224
- - email handling
225
- - Durable Object exports
226
- - auxiliary RPC handlers
227
- - test-specific wiring
306
+ ### Local env-file overlap warning
228
307
 
229
- Devflare pushes in the opposite direction.
308
+ The filename `.env` can be confusing because it may participate in **two different stories**:
230
309
 
231
- It gives each responsibility a natural home and then composes them together.
310
+ 1. Bun may read it at process start and populate `process.env`
311
+ 2. local Cloudflare/Wrangler tooling may also use `.env`-style files for runtime bindings
232
312
 
233
- That has several benefits:
313
+ So the same file can accidentally influence both config-time and runtime behavior.
234
314
 
235
- - lower mental overhead
236
- - easier code generation
237
- - easier code review
238
- - easier testing
239
- - fewer accidental runtime coupling mistakes
240
- - clearer ownership boundaries within a team
315
+ If you want clearer separation, prefer this mental split:
241
316
 
242
- For LLM generation specifically, this matters a lot.
317
+ - use Bun-loaded `.env` only for config-time inputs that you intentionally read via `process.env`
318
+ - use local secret files such as `.dev.vars` for runtime secrets
243
319
 
244
- When generating Devflare code, prefer **separate files with clear responsibilities** over giant all-in-one worker files.
320
+ ### If you need strict control over env loading
321
+
322
+ Because the CLI runs under Bun, Bun’s own env-file controls still matter. If you invoke the CLI through Bun directly, Bun-level flags such as explicit env-file selection or env-file disabling control that part of the story.
245
323
 
246
324
  ---
247
325
 
248
- ## Config example
326
+ ## Top-level config reference
327
+
328
+ This table shows what each top-level config key means, which layer uses it, and whether it becomes part of compiled Wrangler output.
329
+
330
+ | Key | Meaning | Used by | Compiled to `wrangler.jsonc` | Notes |
331
+ |---|---|---|---|---|
332
+ | `name` | Worker name | all phases | Yes | required |
333
+ | `accountId` | Cloudflare account ID | compile, dev, remote integrations | Yes | especially relevant for remote services |
334
+ | `compatibilityDate` | Workers compatibility date | compile/runtime | Yes | defaults to current date |
335
+ | `compatibilityFlags` | Workers compatibility flags | compile/runtime | Yes | Devflare always includes `nodejs_compat` and `nodejs_als` |
336
+ | `files` | handler paths and discovery globs | Devflare discovery/dev/test/build tooling | Partly | `files.fetch` influences compiled `main`; most of `files` is Devflare-only metadata |
337
+ | `bindings` | Cloudflare bindings | compile/dev/test/runtime | Mostly | some bindings are stronger than others, see caveats below |
338
+ | `triggers` | cron triggers | compile/runtime | Yes | currently `crons` |
339
+ | `vars` | non-secret runtime bindings | compile/runtime | Yes | string values only in current schema |
340
+ | `secrets` | secret declarations | typing/validation/runtime expectations | No secret values | declaration layer only |
341
+ | `routes` | Cloudflare deployment routes | compile/deploy | Yes | this is edge routing, not file-based app routing |
342
+ | `wsRoutes` | dev-mode websocket-to-DO proxy rules | local dev | No | Devflare-only dev feature |
343
+ | `assets` | static assets config | compile/dev/runtime | Yes | current native shape is `{ directory, binding? }` |
344
+ | `limits` | worker runtime limits | compile/runtime | Yes | current native shape only models `cpu_ms` |
345
+ | `observability` | worker logs / sampling | compile/runtime | Yes | passes through directly |
346
+ | `migrations` | Durable Object migrations | compile/runtime | Yes | used for DO class evolution |
347
+ | `build` | build metadata/options | schema/dev/build tooling | No direct compiler output | see caveats below |
348
+ | `plugins` | Vite plugin list / build-time extension point | schema/build tooling | No direct compiler output | see caveats below |
349
+ | `env` | environment-specific overrides | Devflare pre-compilation merge layer | Not as nested env blocks | only a subset of top-level keys is supported inside each env block; see the dedicated section |
350
+ | `wrangler.passthrough` | raw Wrangler fields not modeled natively | compiler/deploy | Yes | merged at top level into compiled config |
351
+
352
+ ### Minimal config
249
353
 
250
- This is a good realistic starting point.
354
+ ```ts
355
+ import { defineConfig } from 'devflare'
356
+
357
+ export default defineConfig({
358
+ name: 'hello-worker'
359
+ })
360
+ ```
361
+
362
+ ### Realistic starting config
251
363
 
252
364
  ```ts
253
365
  import { defineConfig } from 'devflare'
@@ -300,13 +412,74 @@ export default defineConfig({
300
412
 
301
413
  ---
302
414
 
303
- ## File-based routing
415
+ ## `files`: conventions, discovery, and what each tool does with them
304
416
 
305
- Devflare supports a file-structured routing model through `files.routes`.
417
+ The `files` section tells Devflare where to find handlers and discovery targets.
306
418
 
307
- That is important because it moves you away from hand-written request dispatch in one file and toward a more maintainable app structure.
419
+ | Key | Shape | Default convention | Meaning |
420
+ |---|---|---|---|
421
+ | `fetch` | `string | false` | `src/fetch.ts` or `src/fetch.js` | main HTTP handler |
422
+ | `queue` | `string | false` | `src/queue.ts` | queue consumer handler |
423
+ | `scheduled` | `string | false` | `src/scheduled.ts` | cron/scheduled handler |
424
+ | `email` | `string | false` | `src/email.ts` | incoming email handler |
425
+ | `durableObjects` | `string | false` | `**/do.*.{ts,js}` | DO class discovery glob |
426
+ | `entrypoints` | `string | false` | `**/ep.*.{ts,js}` | WorkerEntrypoint discovery glob |
427
+ | `workflows` | `string | false` | `**/wf.*.{ts,js}` | Workflow discovery glob |
428
+ | `routes` | `{ dir, prefix? } or false` | no implicit route dir unless configured | file-based HTTP routing |
429
+ | `transport` | `string` | `src/transport.ts` | custom encode/decode transport definitions |
308
430
 
309
- Example:
431
+ ### Discovery rules
432
+
433
+ - discovery globs respect `.gitignore`
434
+ - ignored/generated directories such as `node_modules`, `dist`, and `.devflare` are excluded
435
+ - setting a handler/discovery key to `false` explicitly disables that surface
436
+
437
+ ### Test-context config resolution is more limited than `loadConfig()`
438
+
439
+ `loadConfig()` supports `.ts`, `.mts`, `.js`, and `.mjs` config filenames.
440
+
441
+ However, the current automatic nearest-config search used by `createTestContext()` only looks for:
442
+
443
+ - `devflare.config.ts`
444
+ - `devflare.config.js`
445
+
446
+ That means:
447
+
448
+ - if your project uses `devflare.config.mts` or `devflare.config.mjs`, pass the config path explicitly to `createTestContext()`
449
+ - explicit config paths in `createTestContext()` are resolved relative to the calling test file, not always relative to `process.cwd()`
450
+ - monorepos are safest when tests pass an explicit config path instead of relying on upward auto-discovery
451
+
452
+ ### `files.transport` must export a named `transport`
453
+
454
+ When `createTestContext()` loads `files.transport`, it expects a named `transport` export.
455
+
456
+ If the file exists but does not export `transport`, Devflare currently warns and falls back to testing without custom transport encoding/decoding.
457
+
458
+ ### Very important nuance: conventions are not used identically by every tool
459
+
460
+ Devflare is convention-friendly, but not every subsystem consumes conventions in the exact same way.
461
+
462
+ Current important behavior:
463
+
464
+ - local dev and `createTestContext()` can fall back to conventional handler files when present
465
+ - type generation and discovery use default globs for DOs, entrypoints, and workflows
466
+ - `compileConfig()` only writes Wrangler `main` when `files.fetch` is explicitly a string
467
+
468
+ So the safest rule is:
469
+
470
+ > If a surface matters to build/deploy output, prefer making it explicit in config even if Devflare can discover it conventionally in some other paths.
471
+
472
+ ### `files.routes` vs top-level `routes` vs `wsRoutes`
473
+
474
+ These three are different and should never be conflated.
475
+
476
+ | Key | What it does | Layer |
477
+ |---|---|---|
478
+ | `files.routes` | enables Devflare file-based HTTP route discovery inside your app | Devflare app structure |
479
+ | top-level `routes` | defines Cloudflare deployment route matching like `example.com/*` | Cloudflare deployment config |
480
+ | `wsRoutes` | defines local dev websocket upgrade forwarding to Durable Objects | Devflare dev server |
481
+
482
+ ### File-based routing example
310
483
 
311
484
  ```ts
312
485
  import { defineConfig } from 'devflare'
@@ -322,451 +495,613 @@ export default defineConfig({
322
495
  })
323
496
  ```
324
497
 
325
- Conceptually, this means:
498
+ Typical route-tree mental model:
326
499
 
327
- - route files map onto URL paths
328
- - route handlers can stay focused on their specific endpoint logic
329
- - your HTTP surface can scale without turning `fetch.ts` into a switch-statement graveyard
330
-
331
- If you only need a small Worker, `src/fetch.ts` is perfect.
500
+ ```text
501
+ src/routes/
502
+ ├── index.ts
503
+ ├── users/
504
+ │ ├── index.ts
505
+ │ └── [id].ts
506
+ └── health.ts
507
+ ```
332
508
 
333
- If your HTTP surface is growing, `files.routes` is usually the better model.
509
+ Use `files.routes` when the HTTP surface is large enough that a single `fetch.ts` would become a switch-statement graveyard.
334
510
 
335
511
  ---
336
512
 
337
- ## Queues: produce in one place, consume in another
513
+ ## `bindings`: supported binding types and what they mean
338
514
 
339
- Devflare wants queue production and queue consumption to live in obvious places.
515
+ Bindings are the heart of the runtime contract.
340
516
 
341
- - produce messages from `fetch` or another handler via the bound queue producer
342
- - consume them in `src/queue.ts`
343
- - test them with `cf.queue`
517
+ ### Binding reference
344
518
 
345
- ### Queue config example
519
+ | Binding type | Shape | Compiles natively | Notes |
520
+ |---|---|---|---|
521
+ | `kv` | `Record<string, string>` | Yes | binding name → KV namespace ID |
522
+ | `d1` | `Record<string, string>` | Yes | binding name → D1 database ID |
523
+ | `r2` | `Record<string, string>` | Yes | binding name → bucket name |
524
+ | `durableObjects` | `Record<string, string | { className, scriptName? }>` | Yes | string shorthand or object form, including cross-worker refs |
525
+ | `queues.producers` | `Record<string, string>` | Yes | binding name → queue name |
526
+ | `queues.consumers` | array of consumer objects | Yes | batch size, retries, timeout, DLQ, concurrency, retry delay |
527
+ | `services` | `Record<string, { service, environment?, entrypoint? }>` or `ref().worker` | Mostly | see named-entrypoint caveat below |
528
+ | `ai` | `{ binding }` | Yes | remote-oriented service |
529
+ | `vectorize` | `Record<string, { indexName }>` | Yes | remote-oriented service |
530
+ | `hyperdrive` | `Record<string, { id }>` | Yes | PostgreSQL acceleration |
531
+ | `browser` | `{ binding }` | Yes | browser-rendering support with local shim behavior |
532
+ | `analyticsEngine` | `Record<string, { dataset }>` | Yes | Analytics Engine datasets |
533
+ | `sendEmail` | `Record<string, { destinationAddress? }>` | **Not fully** | schema exists, but support is lighter than core bindings |
346
534
 
347
- ```ts
348
- import { defineConfig } from 'devflare'
535
+ ### Core storage bindings
349
536
 
350
- export default defineConfig({
351
- name: 'tasks-app',
352
- bindings: {
353
- queues: {
354
- producers: {
355
- TASK_QUEUE: 'task-queue'
356
- },
357
- consumers: [
358
- {
359
- queue: 'task-queue',
360
- maxBatchSize: 10,
361
- maxRetries: 3
362
- }
363
- ]
364
- },
365
- kv: {
366
- RESULTS: 'results-kv-id'
367
- }
537
+ These are straightforward binding-name maps:
538
+
539
+ ```ts
540
+ bindings: {
541
+ kv: {
542
+ CACHE: 'cache-kv-id'
543
+ },
544
+ d1: {
545
+ DB: 'db-id'
546
+ },
547
+ r2: {
548
+ FILES: 'files-bucket'
368
549
  }
369
- })
550
+ }
370
551
  ```
371
552
 
372
- ### Producer example from `fetch`
553
+ ### Durable Objects
554
+
555
+ Durable Object bindings support both shorthand and object form.
556
+
557
+ Shorthand local form:
373
558
 
374
559
  ```ts
375
- import { env } from 'devflare'
560
+ bindings: {
561
+ durableObjects: {
562
+ COUNTER: 'Counter'
563
+ }
564
+ }
565
+ ```
376
566
 
377
- export default async function fetch(request: Request): Promise<Response> {
378
- const url = new URL(request.url)
567
+ Explicit form:
379
568
 
380
- if (url.pathname === '/tasks') {
381
- const task = {
382
- id: crypto.randomUUID(),
383
- type: 'resize-image'
569
+ ```ts
570
+ bindings: {
571
+ durableObjects: {
572
+ COUNTER: {
573
+ className: 'Counter'
384
574
  }
385
-
386
- await env.TASK_QUEUE.send(task)
387
- return Response.json({ queued: true, task })
388
575
  }
389
-
390
- return new Response('Not found', { status: 404 })
391
576
  }
392
577
  ```
393
578
 
394
- ### Consumer example in `src/queue.ts`
579
+ Cross-worker form uses `scriptName` or a `ref()`-produced DO binding.
395
580
 
396
- ```ts
397
- import type { MessageBatch } from '@cloudflare/workers-types'
581
+ ### Queues
582
+
583
+ Producers and consumers are separated intentionally.
398
584
 
399
- type Task = {
400
- id: string
401
- type: string
585
+ ```ts
586
+ bindings: {
587
+ queues: {
588
+ producers: {
589
+ TASK_QUEUE: 'task-queue'
590
+ },
591
+ consumers: [
592
+ {
593
+ queue: 'task-queue',
594
+ maxBatchSize: 10,
595
+ maxBatchTimeout: 5,
596
+ maxRetries: 3,
597
+ deadLetterQueue: 'task-queue-dlq'
598
+ }
599
+ ]
600
+ }
402
601
  }
602
+ ```
603
+
604
+ Important distinction:
605
+
606
+ - `queues.producers` creates runtime bindings that your code can call
607
+ - `queues.consumers` configures delivery behavior for queue names and does not create extra env binding names by itself
608
+
609
+ ### Services and entrypoints
403
610
 
404
- export default async function queue(
405
- batch: MessageBatch<Task>,
406
- env: DevflareEnv
407
- ): Promise<void> {
408
- for (const message of batch.messages) {
409
- try {
410
- await env.RESULTS.put(
411
- `result:${message.body.id}`,
412
- JSON.stringify({ status: 'completed', type: message.body.type })
413
- )
414
- message.ack()
415
- } catch {
416
- message.retry()
611
+ Service bindings can be written directly or via `ref()`.
612
+
613
+ Direct form:
614
+
615
+ ```ts
616
+ bindings: {
617
+ services: {
618
+ AUTH: {
619
+ service: 'auth-worker'
417
620
  }
418
621
  }
419
622
  }
420
623
  ```
421
624
 
422
- Authoring guidance:
625
+ Current compiler nuance:
423
626
 
424
- - put queue consumers in `src/queue.ts` unless you have a strong reason not to
425
- - keep message bodies plain and serializable
426
- - use `message.ack()` on success and `message.retry()` on failure
427
- - bind persistence or downstream systems in config rather than hiding them in globals
627
+ - the service-binding schema and `ref().worker('EntrypointName')` surface can model named entrypoints
628
+ - the current config compiler emits `service` and optional `environment`
629
+ - it does **not** currently emit a native compiled `entrypoint` field into generated Wrangler config
428
630
 
429
- ---
631
+ So named-entrypoint service bindings should be treated as a typed/public surface with deployment-time caution.
632
+
633
+ `ref()` form:
634
+
635
+ ```ts
636
+ import { defineConfig, ref } from 'devflare'
430
637
 
431
- ## Email: receiving and sending
638
+ const auth = ref(() => import('../auth/devflare.config'))
432
639
 
433
- Devflare supports two sides of email: **receiving** incoming messages and **sending** outgoing ones.
640
+ export default defineConfig({
641
+ name: 'gateway',
642
+ bindings: {
643
+ services: {
644
+ AUTH: auth.worker,
645
+ ADMIN: auth.worker('AdminEntrypoint')
646
+ }
647
+ }
648
+ })
649
+ ```
434
650
 
435
- ### Receiving email
651
+ ### Browser rendering
436
652
 
437
- Export an `email` function from `src/email.ts`. Devflare wires it as the worker's incoming email handler.
653
+ Browser rendering is one of the clearest examples of Devflare adding meaningful orchestration on top of a local Workers runtime.
438
654
 
439
655
  ```ts
440
- // src/email.ts
441
- import { env } from 'devflare'
442
- import type { ForwardableEmailMessage } from '@cloudflare/workers-types'
443
-
444
- export async function email(message: ForwardableEmailMessage): Promise<void> {
445
- const id = crypto.randomUUID()
446
-
447
- // Log the email to KV
448
- await env.EMAIL_LOG.put(
449
- `email:${id}`,
450
- JSON.stringify({
451
- from: message.from,
452
- to: message.to,
453
- receivedAt: new Date().toISOString()
454
- })
455
- )
456
-
457
- // Forward every incoming message to admin
458
- await message.forward('admin@example.com')
656
+ bindings: {
657
+ browser: {
658
+ binding: 'BROWSER'
659
+ }
459
660
  }
460
661
  ```
461
662
 
462
- The `ForwardableEmailMessage` object also supports `message.reply(replyMessage)` for auto-responses.
663
+ In local dev, Devflare adds browser-shim orchestration so browser workflows can participate in the same system as the rest of your worker app.
463
664
 
464
- ### Sending email (the `sendEmail` binding)
665
+ ### AI and Vectorize
465
666
 
466
- To send outgoing email, declare a `sendEmail` binding in config. Each key becomes a typed `env` property of type `SendEmail`.
667
+ These services are fundamentally remote-oriented.
467
668
 
468
- The value object accepts an optional `destinationAddress`:
669
+ Devflare can type them, configure them, and help you test them intentionally, but it does **not** pretend they are fully local equivalents of KV or D1.
469
670
 
470
- - **With `destinationAddress`** every email sent through that binding is locked to that recipient.
471
- - **Without it (`{}`)** — you choose the recipient at send time.
671
+ In the current high-level test setup, these are also the bindings that switch to Cloudflare-backed remote proxies when remote mode is enabled. They are the clearest “remote-only” part of the public testing story.
472
672
 
473
- ```ts
474
- import { defineConfig } from 'devflare'
673
+ ### `sendEmail`
475
674
 
476
- export default defineConfig({
477
- name: 'support-mail',
478
- bindings: {
479
- kv: {
480
- EMAIL_LOG: 'email-log-kv-id'
481
- },
482
- sendEmail: {
483
- // Locked destination — always delivers to admin
484
- ADMIN_EMAIL: { destinationAddress: 'admin@example.com' },
675
+ `sendEmail` is present in the schema and examples, with this current shape:
485
676
 
486
- // Open destination — specify per message
487
- EMAIL: {}
677
+ ```ts
678
+ bindings: {
679
+ sendEmail: {
680
+ ADMIN_EMAIL: {
681
+ destinationAddress: 'admin@example.com'
488
682
  }
489
683
  }
490
- })
684
+ }
491
685
  ```
492
686
 
493
- This produces:
687
+ However, this binding is currently **not wired through the compiler/types/runtime helpers as completely as core bindings like KV, D1, R2, DOs, queues, and services**. Treat it as a lighter/partial area and validate actual generated output before relying on it in deployment-critical paths.
688
+
689
+ ---
494
690
 
495
- - `env.ADMIN_EMAIL: SendEmail` hardcoded to `admin@example.com`
496
- - `env.EMAIL: SendEmail` — recipient provided at send time
691
+ ## `vars`, `secrets`, and generated types
497
692
 
498
- ### Testing email
693
+ ### `vars`
499
694
 
500
- Use the `email` helper from `devflare/test`:
695
+ Current Devflare schema models `vars` as:
501
696
 
502
697
  ```ts
503
- import { email } from 'devflare/test'
698
+ Record<string, string>
699
+ ```
504
700
 
505
- const response = await email.send({
506
- from: 'sender@example.com',
507
- to: 'recipient@example.com',
508
- subject: 'Test message',
509
- body: 'Hello from the test suite.'
510
- })
701
+ That is stricter than the broadest possible Wrangler configuration story.
702
+
703
+ So in Devflare today:
511
704
 
512
- expect(response.ok).toBe(true)
705
+ - treat `vars` as **string-valued bindings**
706
+ - serialize more complex values yourself if needed
707
+ - do not assume arbitrary JSON-valued vars are a first-class Devflare config feature
708
+
709
+ ### `secrets`
710
+
711
+ Current Devflare schema models each secret as:
712
+
713
+ ```ts
714
+ { required?: boolean }
513
715
  ```
514
716
 
515
- Authoring guidance:
717
+ That means the native configuration is about **declaring expected secret names and whether they are required**, not about storing the secret values themselves.
516
718
 
517
- - use `src/email.ts` for incoming mail handling
518
- - use `message.forward(...)` for routing workflows
519
- - use `message.reply(...)` when implementing auto-replies
520
- - use `sendEmail` bindings with `destinationAddress` when the recipient is always the same
521
- - use `sendEmail` bindings with `{}` when you need flexible per-message recipients
522
- - log metadata to KV or D1 if you need observability or replay/debugging context
719
+ One subtle but important default: `required` defaults to `true`.
523
720
 
524
- ---
721
+ So this:
525
722
 
526
- ## Browser rendering is a real example of Devflare extending Miniflare
723
+ ```ts
724
+ secrets: {
725
+ API_KEY: {}
726
+ }
727
+ ```
527
728
 
528
- This is worth emphasizing.
729
+ means “`API_KEY` is a required runtime secret”, not “optional secret with no requirements”.
529
730
 
530
- Devflare supports **browser rendering** locally via a browser shim layer.
731
+ ### `devflare types`
531
732
 
532
- That matters because browser rendering is not something Miniflare gives you directly as a complete local developer workflow.
733
+ `devflare types` generates `env.d.ts` and uses config/discovery information to generate:
533
734
 
534
- With Devflare, browser rendering becomes part of the same system as the rest of your bindings and local development story:
735
+ - a global `DevflareEnv` interface
736
+ - binding members for `vars`
737
+ - binding members for `secrets`
738
+ - discovered WorkerEntrypoints
739
+ - entrypoint union types such as `Entrypoints`
740
+ - service/DO typing for cross-worker references
535
741
 
536
- - declare the browser binding in config
537
- - run the app through Devflare’s orchestration
538
- - use the browser capability locally with the rest of your worker system
742
+ Recommended invocation:
539
743
 
540
- This is exactly the kind of feature that demonstrates the Devflare philosophy:
744
+ ```text
745
+ bunx --bun devflare types
746
+ ```
541
747
 
542
- > take a low-level runtime foundation, then add the higher-level orchestration developers actually need
748
+ After generation, project code can use the global `DevflareEnv` interface and typed `ref()` entrypoints.
543
749
 
544
750
  ---
545
751
 
546
- ## Durable Objects and multi-worker systems
752
+ ## Environment overrides via `config.env`
753
+
754
+ Devflare’s `env` field is a **Devflare-level merge layer**.
755
+
756
+ It is not simply a verbatim mirror of raw Wrangler `[env.someName]` sections.
757
+
758
+ ### How it works
759
+
760
+ When you call `compileConfig(config, environment)` or run CLI commands with `--env <name>`, Devflare:
547
761
 
548
- Devflare is designed to keep multi-worker and DO-heavy systems manageable.
762
+ 1. takes the base config
763
+ 2. reads `config.env?.[name]`
764
+ 3. merges that override object into the base config with `defu`
765
+ 4. compiles the **resolved result**
549
766
 
550
- ### Local Durable Objects
767
+ That means omitted nested fields inherit from the base config.
551
768
 
552
- Bind them in config:
769
+ ### Keys that can actually appear inside `env`
770
+
771
+ The current `env` schema supports overrides for these keys:
772
+
773
+ - `name`
774
+ - `compatibilityDate`
775
+ - `compatibilityFlags`
776
+ - `files`
777
+ - `bindings`
778
+ - `triggers`
779
+ - `vars`
780
+ - `secrets`
781
+ - `routes`
782
+ - `assets`
783
+ - `limits`
784
+ - `observability`
785
+ - `migrations`
786
+ - `build`
787
+ - `wrangler`
788
+
789
+ Not currently supported inside `config.env.<name>`:
790
+
791
+ - `accountId`
792
+ - `wsRoutes`
793
+ - `plugins`
794
+
795
+ So if you need environment-specific values for one of those unsupported keys, compute them in the outer `defineConfig()` function yourself instead of assuming `config.env` can hold them.
796
+
797
+ ### Merge semantics are `defu`, not “replace the base object”
798
+
799
+ Devflare currently resolves environments with:
553
800
 
554
801
  ```ts
555
- bindings: {
556
- durableObjects: {
557
- COUNTER: 'Counter'
558
- }
559
- }
802
+ defu(config.env[environment], config)
560
803
  ```
561
804
 
562
- Put their classes in `do.*.ts` files.
805
+ That means all of the following are true:
563
806
 
564
- ### Complete local Durable Object example
807
+ - leftmost values win when both sides define the same scalar field
808
+ - nested objects inherit omitted keys from the base config
809
+ - arrays are concatenated rather than replaced, with env-specific array items first and base items appended afterward
810
+ - `null` and `undefined` are skipped rather than used as a deletion mechanism
565
811
 
566
- If you need to generate a normal local Durable Object and call it from HTTP code, prefer this shape.
812
+ ### Example
567
813
 
568
814
  ```ts
569
- // devflare.config.ts
570
815
  import { defineConfig } from 'devflare'
571
816
 
572
817
  export default defineConfig({
573
- name: 'sessions-app',
818
+ name: 'app',
819
+ triggers: {
820
+ crons: ['0 0 * * *']
821
+ },
822
+ vars: {
823
+ API_URL: 'https://api.example.com',
824
+ DEBUG: 'false'
825
+ },
574
826
  bindings: {
575
- durableObjects: {
576
- SESSION: 'SessionStore'
827
+ kv: {
828
+ CACHE: 'prod-cache-id'
829
+ }
830
+ },
831
+ env: {
832
+ dev: {
833
+ triggers: {
834
+ crons: ['*/5 * * * *']
835
+ },
836
+ vars: {
837
+ DEBUG: 'true'
838
+ },
839
+ bindings: {
840
+ kv: {
841
+ CACHE: 'dev-cache-id'
842
+ }
843
+ }
577
844
  }
578
845
  }
579
846
  })
580
847
  ```
581
848
 
582
- ```ts
583
- // src/do.session.ts
584
- import { DurableObject } from 'cloudflare:workers'
849
+ Selecting `dev` gives you a merged result equivalent to:
585
850
 
586
- export class SessionStore extends DurableObject<DevflareEnv> {
587
- private data = new Map<string, string>()
851
+ - `vars.API_URL = 'https://api.example.com'`
852
+ - `vars.DEBUG = 'true'`
853
+ - `bindings.kv.CACHE = 'dev-cache-id'`
854
+ - `triggers.crons = ['*/5 * * * *', '0 0 * * *']`
588
855
 
589
- getValue(key: string): string | null {
590
- return this.data.get(key) ?? null
591
- }
856
+ That last point surprises people. If you expected the env-specific cron array to **replace** the base array, that is not how the current merge works.
592
857
 
593
- setValue(key: string, value: string): void {
594
- this.data.set(key, value)
595
- }
858
+ ### Practical rule for arrays
859
+
860
+ If an array should be totally different per environment, do **not** rely on `config.env` to replace it.
861
+
862
+ Instead, compute the final value in `defineConfig(() => ...)` yourself and return the already-resolved array.
863
+
864
+ ### `wrangler.passthrough` is applied after compilation
865
+
866
+ Another important precedence rule: after Devflare compiles the resolved config, it applies `wrangler.passthrough` with a top-level `Object.assign()`.
867
+
868
+ That means passthrough values can overwrite fields that Devflare just compiled.
869
+
870
+ This is powerful, but it also means `wrangler.passthrough` is an escape hatch with override power, not just a harmless bag of extra settings.
871
+
872
+ ### Why this matters
873
+
874
+ Raw Wrangler environments have their own rules, and some sections in Wrangler are famously non-inheritable.
875
+
876
+ Devflare’s `config.env` is more ergonomic:
877
+
878
+ - you write only the differences
879
+ - Devflare resolves a single environment-specific config first
880
+ - then it compiles the resolved config
881
+
882
+ So the correct mental model is:
883
+
884
+ > `config.env` is a Devflare **pre-compilation override system**, not just a literal nested dump into Wrangler.
885
+
886
+ ---
887
+
888
+ ## `routes`, `assets`, `observability`, `limits`, `migrations`, and passthrough
596
889
 
597
- clearAll(): void {
598
- this.data.clear()
890
+ ### Deployment `routes`
891
+
892
+ Top-level `routes` are Cloudflare deployment routes.
893
+
894
+ ```ts
895
+ routes: [
896
+ {
897
+ pattern: 'example.com/*',
898
+ zone_name: 'example.com'
599
899
  }
600
- }
900
+ ]
601
901
  ```
602
902
 
603
- ```ts
604
- // src/fetch.ts
605
- import { env } from 'devflare'
903
+ These are about **which hostnames/paths invoke the worker in deployment**.
606
904
 
607
- export default async function fetch(request: Request): Promise<Response> {
608
- const url = new URL(request.url)
609
- const sessionId = url.searchParams.get('session') ?? 'default'
905
+ They are not related to file-based app routing.
610
906
 
611
- const id = env.SESSION.idFromName(sessionId)
612
- const session = env.SESSION.get(id)
907
+ ### `wsRoutes`
613
908
 
614
- if (url.pathname === '/set') {
615
- const key = url.searchParams.get('key') ?? 'name'
616
- const value = url.searchParams.get('value') ?? 'Arthur'
617
- await session.setValue(key, value)
618
- return Response.json({ ok: true })
619
- }
909
+ `wsRoutes` are for Devflare local development. They describe how websocket upgrade requests should be forwarded into Durable Objects during dev.
620
910
 
621
- if (url.pathname === '/get') {
622
- const key = url.searchParams.get('key') ?? 'name'
623
- const value = await session.getValue(key)
624
- return Response.json({ value })
625
- }
911
+ Current shape:
626
912
 
627
- if (url.pathname === '/clear') {
628
- await session.clearAll()
629
- return Response.json({ ok: true })
913
+ ```ts
914
+ wsRoutes: [
915
+ {
916
+ pattern: '/chat/api',
917
+ doNamespace: 'CHAT_ROOM',
918
+ idParam: 'roomId',
919
+ forwardPath: '/websocket'
630
920
  }
921
+ ]
922
+ ```
923
+
924
+ These are **not** compiled into Wrangler config.
631
925
 
632
- return new Response('Not found', { status: 404 })
926
+ ### `assets`
927
+
928
+ Current native Devflare assets shape is:
929
+
930
+ ```ts
931
+ assets: {
932
+ directory: './public',
933
+ binding: 'ASSETS'
633
934
  }
634
935
  ```
635
936
 
636
- Important authoring notes:
937
+ The native modeled shape is currently focused on `directory` plus optional binding access.
637
938
 
638
- - extend `DurableObject` from `cloudflare:workers`
639
- - bind the namespace in `bindings.durableObjects`
640
- - put the class in a `do.*.ts` file so discovery works naturally
641
- - create a stub with `env.SESSION.get(env.SESSION.idFromName(...))`
642
- - keep RPC-style method inputs and outputs simple and serializable
939
+ If you need more advanced asset-related Wrangler fields, use `wrangler.passthrough`.
643
940
 
644
- ### Transport layer example: Devflare encodes and decodes for you
941
+ ### `observability`
645
942
 
646
- Use `src/transport.ts` when a Worker or Durable Object returns custom classes that should survive supported RPC/test boundaries as real instances.
943
+ Current native shape:
647
944
 
648
945
  ```ts
649
- // src/DoubleableNumber.ts
650
- export class DoubleableNumber {
651
- value: number
652
-
653
- constructor(n: number) {
654
- this.value = n
655
- }
656
-
657
- get double() {
658
- return this.value * 2
659
- }
946
+ observability: {
947
+ enabled: true,
948
+ head_sampling_rate: 0.1
660
949
  }
661
950
  ```
662
951
 
663
- ```ts
664
- // src/transport.ts
665
- import { DoubleableNumber } from './DoubleableNumber'
952
+ This compiles directly.
666
953
 
667
- export const transport = {
668
- DoubleableNumber: {
669
- encode: (v: unknown) => v instanceof DoubleableNumber && v.value,
670
- decode: (v: number) => new DoubleableNumber(v)
671
- }
954
+ ### `limits`
955
+
956
+ Current native shape only models:
957
+
958
+ ```ts
959
+ limits: {
960
+ cpu_ms: 50
672
961
  }
673
962
  ```
674
963
 
675
- ```ts
676
- // src/do.counter.ts
677
- import { DoubleableNumber } from './DoubleableNumber'
964
+ If you need Wrangler/Cloudflare limit-related fields outside that native shape, use `wrangler.passthrough`.
678
965
 
679
- export class Counter {
680
- private count = 0
966
+ ### `migrations`
681
967
 
682
- getValue(): DoubleableNumber {
683
- return new DoubleableNumber(this.count)
684
- }
968
+ Durable Object migrations compile directly and are required when evolving DO classes over time.
969
+
970
+ Current supported migration keys include:
971
+
972
+ - `tag`
973
+ - `new_classes`
974
+ - `renamed_classes`
975
+ - `deleted_classes`
976
+ - `new_sqlite_classes`
977
+
978
+ ### `wrangler.passthrough`
685
979
 
686
- increment(n: number = 1): DoubleableNumber {
687
- this.count += n
688
- return new DoubleableNumber(this.count)
980
+ Use `wrangler.passthrough` for supported Wrangler fields that Devflare does not model natively.
981
+
982
+ Example:
983
+
984
+ ```ts
985
+ wrangler: {
986
+ passthrough: {
987
+ placement: { mode: 'smart' },
988
+ workers_dev: true
689
989
  }
690
990
  }
691
991
  ```
692
992
 
693
- ```ts
694
- // tests/counter.test.ts
695
- import { beforeAll, afterAll, describe, expect, test } from 'bun:test'
696
- import { createTestContext } from 'devflare/test'
697
- import { env } from 'devflare'
698
- import { DoubleableNumber } from '../src/DoubleableNumber'
993
+ The compiler merges these keys directly into the top level of the generated Wrangler config.
699
994
 
700
- beforeAll(() => createTestContext())
701
- afterAll(() => env.dispose())
995
+ This is the correct escape hatch when native Devflare config is intentionally narrower than Wrangler’s full surface.
702
996
 
703
- describe('counter transport', () => {
704
- test('result is decoded back into a class instance', async () => {
705
- const id = env.COUNTER.idFromName('main')
706
- const counter = env.COUNTER.get(id)
707
- const result = await counter.increment(2)
997
+ ---
708
998
 
709
- expect(result).toBeInstanceOf(DoubleableNumber)
710
- expect(result.double).toBe(4)
711
- })
712
- })
713
- ```
999
+ ## `build` and `plugins`
1000
+
1001
+ Devflare’s schema includes:
714
1002
 
715
- What Devflare is doing for the developer:
1003
+ - `build`
1004
+ - `plugins`
716
1005
 
717
- 1. your DO returns `new DoubleableNumber(...)`
718
- 2. Devflare finds a matching encoder in `src/transport.ts`
719
- 3. the value is serialized while crossing the boundary
720
- 4. Devflare applies the decoder on the receiving side
721
- 5. your code gets a real `DoubleableNumber` instance back
1006
+ Current `build` shape includes:
722
1007
 
723
- This means the developer writes the codec once and then works with real domain objects rather than manual serialization code.
1008
+ - `target`
1009
+ - `minify`
1010
+ - `sourcemap`
1011
+ - `rolldownOptions`
724
1012
 
725
- ### Cross-worker references
1013
+ However, it is important to understand the current maturity of these fields:
726
1014
 
727
- Use `ref()` when one worker depends on another worker or another worker’s DO bindings.
1015
+ - they exist in the config schema
1016
+ - they are meaningful as project-level Devflare metadata and extension points
1017
+ - but the default CLI build/deploy path still primarily runs `bunx vite build`
1018
+ - they are **not** emitted directly by `compileConfig()` into `wrangler.jsonc`
1019
+
1020
+ So do not assume that every field in `build` or every item in `plugins` is already consumed by every CLI/build path.
1021
+
1022
+ When these fields matter, validate the actual Vite/build behavior in your project.
1023
+
1024
+ ---
1025
+
1026
+ ## `ref()`: multi-worker composition without stringly-typed glue
1027
+
1028
+ Use `ref()` when one worker depends on another worker config.
1029
+
1030
+ Supported forms include:
1031
+
1032
+ ```ts
1033
+ const auth = ref(() => import('../auth/devflare.config'))
1034
+ ```
1035
+
1036
+ ```ts
1037
+ const auth = ref('custom-auth-name', () => import('../auth/devflare.config'))
1038
+ ```
1039
+
1040
+ From a `ref()` result you can access:
1041
+
1042
+ - `.name`
1043
+ - `.config`
1044
+ - `.configPath`
1045
+ - `.worker`
1046
+ - `.worker('EntrypointName')`
1047
+ - uppercase DO binding access like `.COUNTER`
1048
+ - `.resolve()` for manual resolution
1049
+
1050
+ ### Service-binding example
728
1051
 
729
1052
  ```ts
730
1053
  import { defineConfig, ref } from 'devflare'
731
1054
 
732
- const authWorker = ref(() => import('../auth/devflare.config'))
1055
+ const auth = ref(() => import('../auth/devflare.config'))
733
1056
 
734
1057
  export default defineConfig({
735
1058
  name: 'gateway',
736
1059
  bindings: {
737
1060
  services: {
738
- AUTH: authWorker.worker
1061
+ AUTH: auth.worker,
1062
+ ADMIN: auth.worker('AdminEntrypoint')
1063
+ },
1064
+ durableObjects: {
1065
+ AUTH_CACHE: auth.AUTH_CACHE
739
1066
  }
740
1067
  }
741
1068
  })
742
1069
  ```
743
1070
 
744
- This is cleaner than scattering worker names and entrypoint names by hand throughout the codebase.
1071
+ Why `ref()` matters:
1072
+
1073
+ - worker names stay centralized
1074
+ - cross-worker DO bindings stay typed
1075
+ - entrypoint names can be typed from generated `Entrypoints`
1076
+ - multi-worker systems avoid magic strings scattered everywhere
745
1077
 
746
1078
  ---
747
1079
 
748
- ## Runtime-safe helpers
1080
+ ## Unified `env`
749
1081
 
750
- Import runtime-only utilities from `devflare/runtime` when the code runs inside Workers.
1082
+ Importing `env` from the main package gives you a **unified environment proxy**.
751
1083
 
752
- Important helpers include:
1084
+ ```ts
1085
+ import { env } from 'devflare'
1086
+ ```
753
1087
 
754
- - `sequence()`
755
- - `resolve()`
756
- - `pipe()`
757
- - context-related utilities
1088
+ Current resolution order is:
758
1089
 
759
- Use this path when you want runtime-safe middleware composition without pulling in Node-oriented orchestration code.
1090
+ 1. request-scoped context
1091
+ 2. test context
1092
+ 3. bridge env
760
1093
 
761
- ---
1094
+ That means the same import can work across:
762
1095
 
763
- ## Testing model
1096
+ - real request handling
1097
+ - test contexts created by `devflare/test`
1098
+ - bridge-backed local flows outside the request callback itself
764
1099
 
765
- Devflare testing is one of the library’s biggest strengths.
1100
+ ### What `env.dispose()` is for
766
1101
 
767
- ### Integration-style local tests
1102
+ `env.dispose()` is primarily a **test cleanup hook**.
768
1103
 
769
- Use `createTestContext()` when you want a real local Devflare environment backed by Miniflare orchestration.
1104
+ Typical test pattern:
770
1105
 
771
1106
  ```ts
772
1107
  import { beforeAll, afterAll, describe, expect, test } from 'bun:test'
@@ -784,79 +1119,509 @@ describe('worker', () => {
784
1119
  })
785
1120
  ```
786
1121
 
787
- ### Mock/unit-style tests
1122
+ ### When to use imported `env` vs handler parameters
1123
+
1124
+ Both are valid patterns.
1125
+
1126
+ - use handler parameters when you want explicit dependency flow
1127
+ - use `import { env } from 'devflare'` when it improves ergonomics, shared helper usage, or test symmetry
1128
+
1129
+ ---
1130
+
1131
+ ## Testing model
1132
+
1133
+ Devflare testing is one of the strongest parts of the package, but there are **two different layers** and the helpers are not all symmetric.
1134
+
1135
+ ### Main testing entrypoint
1136
+
1137
+ Use `devflare/test`.
1138
+
1139
+ The most important exports are:
1140
+
1141
+ - `createTestContext`
1142
+ - `cf`
1143
+ - `email`
1144
+ - `queue`
1145
+ - `scheduled`
1146
+ - `worker`
1147
+ - `tail`
1148
+ - `shouldSkip`
1149
+ - `isRemoteModeEnabled`
1150
+ - mock helpers such as `createMockKV`, `createMockD1`, `createMockR2`, `createMockQueue`, `createMockEnv`
1151
+ - bridge helpers such as `createBridgeTestContext` and `testEnv`
1152
+
1153
+ ### `createTestContext()` — the recommended high-level test harness
1154
+
1155
+ `createTestContext()` is the recommended default for most app and integration tests.
1156
+
1157
+ Important behavior:
1158
+
1159
+ - it resolves config relative to the **calling test file** when you pass an explicit config path
1160
+ - when you do not pass a config path, it searches upward from the caller directory for the nearest supported config file used by the test harness auto-search
1161
+ - it sets up the unified test env used by `import { env } from 'devflare'`
1162
+ - it resolves service bindings and cross-worker DO references
1163
+ - it can inject remote AI/Vectorize bindings when remote mode is enabled
1164
+ - it can fall back to conventional handler files for `fetch`, `queue`, and `scheduled` when those files exist
1165
+
1166
+ ### Handler helpers are intentionally asymmetric
1167
+
1168
+ Do **not** assume every `cf.*` helper runs handlers the same way.
1169
+
1170
+ | Helper | What it does | Waits for `waitUntil()`? | Important nuance |
1171
+ |---|---|---|---|
1172
+ | `cf.worker.fetch()` | imports the HTTP handler directly and calls default export or named `fetch` | **No** | returns as soon as the handler resolves; background work may still be pending |
1173
+ | `cf.queue.trigger()` | imports the queue handler directly and calls default export or named `queue` | **Yes** | waits for queued `waitUntil()` promises before returning the trigger result |
1174
+ | `cf.scheduled.trigger()` | imports the scheduled handler directly and calls default export or named `scheduled` | **Yes** | cron defaults to `* * * * *` if you do not provide one |
1175
+ | `email.send()` / `cf.email.send()` | posts a raw email to Miniflare’s local email endpoint | runtime-driven | this is how incoming email handlers are triggered locally |
1176
+ | `cf.tail.trigger()` | directly invokes a tail handler and waits for `waitUntil()` | **Yes, if configured** | standard `createTestContext()` currently does **not** auto-configure a tail handler path |
1177
+
1178
+ Two especially important consequences:
1179
+
1180
+ - `cf.worker.fetch()` is **not** a full “wait for all background work” helper
1181
+ - if you need to assert post-response side effects, queue/scheduled tests are more eager about draining `waitUntil()` than worker fetch tests
1182
+
1183
+ ### Email testing has two different directions
1184
+
1185
+ For email, Devflare exposes both an incoming-message trigger and outgoing-message observation.
1186
+
1187
+ - `email.send()` simulates an incoming email delivered to your worker’s `email()` handler
1188
+ - `email.onReceive()` and `email.getSentEmails()` observe outgoing emails produced by `env.EMAIL.send()`, `message.forward()`, or `message.reply()`
1189
+
1190
+ Those are related, but they are not the same action.
1191
+
1192
+ ### Remote mode inside `createTestContext()`
1193
+
1194
+ When remote mode is enabled, the current high-level test context only swaps in remote proxies for:
1195
+
1196
+ - AI
1197
+ - Vectorize
1198
+
1199
+ Other bindings remain local Miniflare-style or locally resolved bindings.
1200
+
1201
+ Also note:
1202
+
1203
+ - `config.vars` are still injected into the test env in remote mode
1204
+ - remote mode is not a blanket “make every binding remote” switch
1205
+
1206
+ ### `createBridgeTestContext()` — lower-level real Miniflare bindings
1207
+
1208
+ `createBridgeTestContext()` is the lower-level integration surface.
1209
+
1210
+ Use it when you want direct access to the real Miniflare bindings rather than the higher-level `cf.*` handler helpers.
1211
+
1212
+ It gives you:
1213
+
1214
+ - a running Miniflare instance
1215
+ - direct `env` bindings
1216
+ - `testEnv` proxy access from `devflare/test`
1217
+ - manual lifecycle control via `stop()` and `reset()`
1218
+
1219
+ Important caveat:
1220
+
1221
+ - `reset()` currently clears KV namespaces, but it does **not** fully reset every other binding type for you
1222
+ - if your test mutates D1, R2, Durable Objects, or similar resources, plan your own cleanup strategy
1223
+
1224
+ ### Transport loading in tests
1225
+
1226
+ If you rely on custom transport encoding/decoding, the test context loads `files.transport` and expects a named `transport` export.
1227
+
1228
+ If that export is missing, Devflare currently warns and continues without custom transport logic.
1229
+
1230
+ ### Tail support caveat
1231
+
1232
+ `tail` is publicly exported from `devflare/test`, but the standard `createTestContext()` currently configures the tail helper with no handler path.
1233
+
1234
+ So you should **not** assume `cf.tail.trigger()` works out of the box in the same way that `cf.worker`, `cf.queue`, or `cf.scheduled` do.
1235
+
1236
+ ### Mock/unit helpers
1237
+
1238
+ For logic tests that should not spin up the full local environment, use:
788
1239
 
789
- Use `createMockTestContext()`, `withTestContext()`, and the mock binding helpers when you want pure logic tests.
1240
+ - `createMockTestContext`
1241
+ - `withTestContext`
1242
+ - mock binding factories such as `createMockKV` and `createMockD1`
790
1243
 
791
- ### Why this is better than ad hoc Miniflare setup
1244
+ ### Skip helpers and remote-only tests
792
1245
 
793
- Devflare gives you a single mental model for testing each handler surface:
1246
+ Some services are intentionally treated as remote-only or cost-sensitive. Devflare includes skip helpers so tests do not accidentally become surprise invoice generators.
794
1247
 
795
- - HTTP via `cf.worker`
796
- - queues via `cf.queue`
797
- - cron via `cf.scheduled`
798
- - email via `cf.email`
799
- - tail events via `cf.tail`
1248
+ Important current behavior:
800
1249
 
801
- That consistency is part of the value. Instead of every project inventing its own test harness, Devflare gives you one coherent interface.
1250
+ - AI and Vectorize are treated as remote-only services
1251
+ - `shouldSkip.<service>` checks remote-mode enablement when relevant, authentication, effective account selection, and usage-limit state
1252
+ - skip decisions are cached per service at module level
1253
+ - operational issues such as auth failures, timeouts, network errors, and rate limits are treated as skip conditions rather than hard test failures
1254
+
1255
+ Use:
1256
+
1257
+ - `shouldSkip`
1258
+ - `devflare/cloudflare` account usage helpers when needed
1259
+
1260
+ Treat `isRemoteModeEnabled()` as a compatibility alias, not the center of the modern test-guard story.
802
1261
 
803
1262
  ---
804
1263
 
805
- ## Vite and SvelteKit integration
1264
+ ## Local dev, build, deploy, and generated artifacts
1265
+
1266
+ ### CLI commands
1267
+
1268
+ Current CLI commands are:
1269
+
1270
+ - `init`
1271
+ - `dev`
1272
+ - `build`
1273
+ - `deploy`
1274
+ - `types`
1275
+ - `doctor`
1276
+ - `account`
1277
+ - `ai`
1278
+ - `remote`
1279
+ - `help`
1280
+ - `version`
1281
+
1282
+ Recommended invocation style:
1283
+
1284
+ ```text
1285
+ bunx --bun devflare <command>
1286
+ ```
1287
+
1288
+ ### `dev`
1289
+
1290
+ `devflare dev` is not “just run Miniflare”.
1291
+
1292
+ It is a combined local development system that includes:
1293
+
1294
+ - Vite dev server behavior
1295
+ - Miniflare-backed Cloudflare bindings
1296
+ - bridge-backed communication between Bun/Node-side tooling and the worker runtime
1297
+ - DO-oriented local orchestration and bundling behavior
1298
+ - websocket-aware proxying for local DO workflows
1299
+ - browser-shim behavior for browser rendering
1300
+ - local migration handling when relevant resources are configured
1301
+
1302
+ Dev command flags include:
1303
+
1304
+ - `--port`
1305
+ - `--persist`
1306
+ - `--verbose`
1307
+ - `--log`
1308
+ - `--log-temp`
1309
+
1310
+ ### Config path and environment selection in CLI commands
1311
+
1312
+ For the current build and deploy flows:
1313
+
1314
+ - config is loaded from the current working directory unless you pass `--config <file>`
1315
+ - `--env <name>` selects `config.env.<name>` during Devflare compilation
1316
+ - on `deploy`, the same `--env <name>` is also forwarded to `wrangler deploy`
1317
+
1318
+ So `--env` on `deploy` affects both the Devflare-side resolution step **and** Wrangler’s deploy command.
1319
+
1320
+ ### `build`
1321
+
1322
+ `devflare build`:
1323
+
1324
+ 1. loads config
1325
+ 2. applies the selected Devflare environment override if `--env` is provided
1326
+ 3. compiles a resolved Wrangler-compatible config
1327
+ 4. writes `wrangler.jsonc`
1328
+ 5. runs `bunx vite build`
1329
+
1330
+ The build subprocess receives `DEVFLARE_BUILD=true` in its environment.
1331
+
1332
+ That means project build code can branch on “Devflare build mode” if it needs to.
1333
+
1334
+ ### `deploy`
1335
+
1336
+ `devflare deploy` performs the same config-resolution step, then builds and runs Wrangler deploy.
806
1337
 
807
- Devflare includes dedicated framework-aware entry points.
1338
+ Important nuances:
1339
+
1340
+ - `--dry-run` prints the resolved compiled config and exits without deploying
1341
+ - `--env <name>` is forwarded to Wrangler after Devflare resolves the same environment locally
1342
+ - unlike `build`, the current deploy command does **not** inject `DEVFLARE_BUILD=true` into the Vite build subprocess
1343
+
1344
+ So do not assume build-time code sees identical environment flags under `build` and `deploy`.
1345
+
1346
+ ### `types`
1347
+
1348
+ `devflare types` generates `env.d.ts`, which is a key part of the typed Devflare workflow.
1349
+
1350
+ ### `doctor`
1351
+
1352
+ `devflare doctor` is a project diagnostics command.
1353
+
1354
+ It currently checks for things such as:
1355
+
1356
+ - a resolvable `devflare.config.*`
1357
+ - config validity
1358
+ - `package.json`
1359
+ - `vite` and `@cloudflare/vite-plugin` dependencies
1360
+ - a Vite config file
1361
+ - `tsconfig.json`
1362
+ - generated `wrangler.jsonc`
1363
+
1364
+ Use it as a quick environment sanity check, not as a proof that every runtime behavior is correct.
1365
+
1366
+ ### Generated artifacts
1367
+
1368
+ Treat these as generated output:
1369
+
1370
+ - project-root `wrangler.jsonc` written by build/deploy flows
1371
+ - `.devflare/` generated output used by the Vite/plugin side of the toolchain
1372
+ - `.devflare/wrangler.jsonc` generated by the Vite plugin integration
1373
+ - generated `env.d.ts`
1374
+
1375
+ Do not hand-edit generated outputs and then expect them to remain the source of truth. The source of truth is still `devflare.config.ts` plus your code files.
1376
+
1377
+ ---
1378
+
1379
+ ## Framework integration
808
1380
 
809
1381
  ### `devflare/vite`
810
1382
 
811
- Use this when you want:
1383
+ This subpath exposes:
1384
+
1385
+ - `devflarePlugin`
1386
+ - `getPluginContext`
1387
+ - `getCloudflareConfig`
1388
+ - `getDevflareConfigs`
1389
+
1390
+ Use it when you need Devflare-aware Vite integration, worker-aware transforms, or auxiliary worker configuration support.
1391
+
1392
+ ### Recommended Vite mental model
1393
+
1394
+ The Vite plugin does more than inject one helper.
1395
+
1396
+ It currently:
812
1397
 
813
- - config compilation during dev and build
814
- - worker-aware transforms
815
- - auxiliary Durable Object worker generation
816
- - websocket-aware proxying for local development
1398
+ - loads and compiles `devflare.config.ts`
1399
+ - injects `__DEVFLARE_WORKER_NAME__` for `workerName`
1400
+ - writes `.devflare/wrangler.jsonc`
1401
+ - discovers Durable Object classes using `files.durableObjects` or the default DO glob
1402
+ - creates an auxiliary `${name}-do` worker config when DO classes are discovered
1403
+ - adds WebSocket proxy rules in dev mode from `wsRoutes` plus any explicit `wsProxyPatterns`
1404
+ - reloads the dev server when the Devflare config changes if config watching is enabled
1405
+
1406
+ If you want the least guesswork in `vite.config.ts`, use `getDevflareConfigs()` and pass the returned config into `@cloudflare/vite-plugin` explicitly.
1407
+
1408
+ ```ts
1409
+ import { defineConfig } from 'vite'
1410
+ import { cloudflare } from '@cloudflare/vite-plugin'
1411
+ import { devflarePlugin, getDevflareConfigs } from 'devflare/vite'
1412
+
1413
+ export default defineConfig(async () => {
1414
+ const { cloudflareConfig, auxiliaryWorkers } = await getDevflareConfigs()
1415
+
1416
+ return {
1417
+ plugins: [
1418
+ devflarePlugin(),
1419
+ cloudflare({
1420
+ config: cloudflareConfig,
1421
+ auxiliaryWorkers: auxiliaryWorkers.length ? auxiliaryWorkers : undefined
1422
+ })
1423
+ ]
1424
+ }
1425
+ })
1426
+ ```
817
1427
 
818
1428
  ### `devflare/sveltekit`
819
1429
 
820
- Use this when your app needs:
1430
+ This subpath exposes:
1431
+
1432
+ - `handle`
1433
+ - `createDevflarePlatform`
1434
+ - `createHandle`
1435
+ - `resetPlatform`
1436
+ - `resetConfigCache`
1437
+ - `isDevflareDev`
1438
+ - `getBridgePort`
821
1439
 
822
- - a Devflare-aware platform object
823
- - integration between SvelteKit and Worker bindings
824
- - local dev behavior that still fits the Devflare model
1440
+ Use it when SvelteKit should run against a Devflare-aware platform object and dev/runtime bridge.
825
1441
 
826
- This is another example of Devflare going beyond raw local runtime emulation. It helps compose framework tooling, worker bindings, auxiliary workers, and local dev behavior into one predictable setup.
1442
+ ### Simplest SvelteKit setup
1443
+
1444
+ The simplest integration is:
1445
+
1446
+ ```ts
1447
+ export { handle } from 'devflare/sveltekit'
1448
+ ```
1449
+
1450
+ Current behavior of the prebuilt `handle`:
1451
+
1452
+ - it only activates when `NODE_ENV !== 'production'` and `DEVFLARE_DEV === 'true'`
1453
+ - it auto-loads binding hints from `devflare.config.ts` in the current working directory
1454
+ - it creates a bridge-backed `platform.env`
1455
+ - it supplies mocked `caches` and a synthetic development `cf` object for local use
1456
+
1457
+ If you need more control, use `createHandle()` or `createDevflarePlatform()` directly.
1458
+
1459
+ That is the better choice when:
1460
+
1461
+ - your project is in a monorepo
1462
+ - `process.cwd()` is not the same directory as the relevant config
1463
+ - you want explicit binding hints
1464
+ - you want a custom enable/disable condition
1465
+
1466
+ Also note:
1467
+
1468
+ - platform instances are cached by bridge URL
1469
+ - `resetPlatform()` and `resetConfigCache()` are mainly test/reset helpers, not everyday app APIs
827
1470
 
828
1471
  ---
829
1472
 
830
- ## Remote-only services
1473
+ ## `devflare/cloudflare`
831
1474
 
832
- Some Cloudflare services are not meaningfully local.
1475
+ `devflare/cloudflare` is the Cloudflare-account helper module.
833
1476
 
834
- Most importantly:
1477
+ Its main export is the `account` helper object, which provides:
835
1478
 
836
- - Workers AI
837
- - Vectorize
1479
+ - authentication inspection
1480
+ - Wrangler-auth inspection
1481
+ - account selection and account summary helpers
1482
+ - resource listing for Workers, KV, D1, R2, Vectorize, and AI
1483
+ - service-status queries
1484
+ - usage tracking and per-service usage summaries
1485
+ - limit enforcement and test-guard helpers
1486
+ - preference helpers for default/global/workspace account selection
1487
+
1488
+ This module is especially useful when writing tooling, diagnostics, or guardrails around remote services.
1489
+
1490
+ ### Account selection precedence
838
1491
 
839
- Devflare treats those as explicit remote-mode concerns rather than pretending they are fully local.
1492
+ The effective account selection order is currently:
840
1493
 
841
- That is a good thing.
1494
+ 1. workspace account
1495
+ 2. global default account
1496
+ 3. primary Cloudflare account
842
1497
 
843
- It keeps the local story honest while still giving you an intentional path to test remote capabilities when needed.
1498
+ ### Where account preferences are stored
844
1499
 
845
- Use the `devflare remote` workflow and guard remote-only tests appropriately.
1500
+ Devflare stores account preferences in more than one place.
1501
+
1502
+ - workspace account: nearest `package.json` under `devflare.accountId`
1503
+ - global default account: local cache at `~/.devflare/preferences.json`
1504
+ - global default account sync: Cloudflare KV namespace titled `devflare-usage`
1505
+
1506
+ That means `devflare account workspace` edits project files, while `devflare account global` updates user-level state and may also sync to Cloudflare-managed storage.
1507
+
1508
+ ### Remote mode storage and precedence
1509
+
1510
+ Remote mode state is also persisted.
1511
+
1512
+ - stored remote-mode config lives at `~/.devflare/remote.json`
1513
+ - `devflare remote enable [minutes]` writes that file and clamps duration to the range `1` to `1440` minutes
1514
+ - `DEVFLARE_REMOTE=1`, `true`, or `yes` takes precedence over the stored file
1515
+
1516
+ This leads to one important practical detail:
1517
+
1518
+ - `devflare remote disable` removes the stored config file
1519
+ - it does **not** unset `DEVFLARE_REMOTE`
1520
+ - so remote mode can still remain active after “disable” if the environment variable is set
1521
+
1522
+ ### Usage guards and test skipping
1523
+
1524
+ The Cloudflare helper layer is also part of Devflare’s remote-test guardrail story.
1525
+
1526
+ Current public behavior includes:
1527
+
1528
+ - usage summaries and per-service limits
1529
+ - `account.canProceedWithTest()` and `account.shouldSkip()` helpers
1530
+ - best-effort behavior when auth/network/API issues occur
1531
+
1532
+ In practice, this means remote-sensitive tests often choose between:
1533
+
1534
+ - running normally
1535
+ - being skipped because remote mode is off
1536
+ - being skipped because the user is unauthenticated, over limits, or hitting operational API issues
846
1537
 
847
1538
  ---
848
1539
 
849
- ## Generated artifacts
1540
+ ## Current caveats and partial areas
1541
+
1542
+ This section exists on purpose. It is better to be accurate than accidentally over-promise.
850
1543
 
851
- Devflare uses `.devflare/` for generated local artifacts.
1544
+ ### Strong, well-defined areas
852
1545
 
853
- Treat that directory as generated output rather than hand-authored source.
1546
+ These are core Devflare strengths today:
1547
+
1548
+ - typed config via `defineConfig()`
1549
+ - config loading/validation/compilation
1550
+ - `vars`
1551
+ - file-based handler conventions
1552
+ - DO bindings and multi-worker references
1553
+ - queues
1554
+ - local test ergonomics
1555
+ - unified `env`
1556
+ - Vite/SvelteKit integration surfaces
1557
+ - browser binding orchestration
1558
+
1559
+ ### Important caveats
1560
+
1561
+ 1. **`secrets` are declaration-oriented, not a secret-value store**
1562
+ - Devflare currently models secret names and `required` status
1563
+ - `required` defaults to `true`
1564
+ - it does not compile secret values into `wrangler.jsonc`
1565
+ - actual secret values must come from runtime secret sources
1566
+
1567
+ 2. **`vars` are string-only in current Devflare config**
1568
+ - do not assume arbitrary JSON-valued vars are a native Devflare feature
1569
+
1570
+ 3. **`config.env` only supports a subset of top-level config keys**
1571
+ - it does not currently support environment-local `accountId`, `wsRoutes`, or `plugins`
1572
+
1573
+ 4. **`config.env` uses `defu` merge semantics**
1574
+ - nested objects inherit from the base config
1575
+ - arrays concatenate instead of replacing
1576
+ - `null` and `undefined` are skipped rather than deleting base values
1577
+
1578
+ 5. **`wrangler.passthrough` is merged last and can override compiled fields**
1579
+ - this is intentional escape-hatch power, but it means passthrough is not purely additive
1580
+
1581
+ 6. **`sendEmail` support is lighter than core bindings**
1582
+ - it exists in the schema and examples
1583
+ - but compiler/types/runtime support is not as complete as KV/D1/R2/DO/queues/services
1584
+
1585
+ 7. **named service entrypoints need caution**
1586
+ - the schema and `ref().worker('Entrypoint')` surface model named entrypoints
1587
+ - but the current config compiler writes `service` and optional `environment`, and does not currently emit a native `entrypoint` field into generated Wrangler config
1588
+ - validate generated output if named-entrypoint deployment wiring matters to your app
1589
+
1590
+ 8. **`createTestContext()` auto-discovery is narrower than `loadConfig()`**
1591
+ - the test-harness auto-search currently looks for `devflare.config.ts` and `devflare.config.js`
1592
+ - projects using `.mts` or `.mjs` should pass an explicit config path
1593
+
1594
+ 9. **test helpers do not all model background work the same way**
1595
+ - `cf.worker.fetch()` does not wait for `waitUntil()` promises
1596
+ - queue and scheduled helpers do wait for `waitUntil()`
1597
+
1598
+ 10. **`cf.tail.trigger()` is public, but standard `createTestContext()` does not auto-wire it today**
1599
+ - treat tail testing as a more manual/partial area than worker, queue, or scheduled testing
1600
+
1601
+ 11. **`createBridgeTestContext().reset()` is not a full universal reset**
1602
+ - KV is cleared
1603
+ - other binding types may still need test-specific cleanup
1604
+
1605
+ 12. **workflows are a lighter area than fetch/queue/DO/entrypoints**
1606
+ - workflow discovery naming is modeled
1607
+ - but the broader runtime/compiler/test story is less mature than the most central surfaces
1608
+
1609
+ 13. **`build` and `plugins` are schema-level extension points, not guaranteed end-to-end behavior in every path**
1610
+ - the default build/deploy commands still center on Vite output
1611
+ - verify actual project behavior when relying on these fields
1612
+
1613
+ 14. **native config intentionally models only part of Wrangler’s total surface**
1614
+ - for unsupported or not-yet-modeled Wrangler keys, use `wrangler.passthrough`
1615
+
1616
+ This is the right way to think about Devflare:
1617
+
1618
+ > Prefer native Devflare config where it exists, use passthrough for unsupported Wrangler features, and do not confuse schema presence with complete end-to-end implementation maturity.
854
1619
 
855
1620
  ---
856
1621
 
857
- ## Recommended project structure
1622
+ ## Recommended project shape
858
1623
 
859
- Here is a strong default layout for most projects.
1624
+ This is a strong default layout for many Devflare apps:
860
1625
 
861
1626
  ```text
862
1627
  .
@@ -876,52 +1641,53 @@ Here is a strong default layout for most projects.
876
1641
  └── worker.test.ts
877
1642
  ```
878
1643
 
879
- You will not always need every file.
880
-
881
- But this shape captures the Devflare philosophy well:
1644
+ You will not always need every file, but this shape matches the Devflare mental model well:
882
1645
 
883
- - each capability gets an isolated home
884
- - config stays declarative
1646
+ - config is declarative
1647
+ - each runtime surface has a natural home
885
1648
  - tests target named surfaces
886
- - local orchestration is handled by Devflare instead of bespoke glue
1649
+ - multi-worker concerns can be expressed explicitly with `ref()`
887
1650
 
888
1651
  ---
889
1652
 
890
1653
  ## Guidance for LLMs generating Devflare code
891
1654
 
892
- When generating Devflare projects or edits, follow these rules.
893
-
894
- ### Prefer conventions first
1655
+ When generating Devflare code, follow these rules.
895
1656
 
896
- Only override paths when there is a real reason.
1657
+ 1. **Prefer `defineConfig()`**
1658
+ - do not invent ad hoc config shapes when a normal Devflare config is appropriate
897
1659
 
898
- ### Prefer separation of responsibilities
1660
+ 2. **Prefer separation of concerns**
1661
+ - do not merge fetch, queue, scheduled, email, DOs, routes, and entrypoints into one file unless explicitly asked
899
1662
 
900
- Do **not** collapse fetch, queue, scheduled, email, DOs, and entrypoints into a single file unless the task explicitly demands that shape.
1663
+ 3. **Prefer explicit config for deploy-critical surfaces**
1664
+ - conventions are great, but explicit `files.fetch`, bindings, routes, and migrations are safer when deployment behavior matters
901
1665
 
902
- ### Prefer `defineConfig()`
1666
+ 4. **Use `vars` only for non-secret runtime configuration**
1667
+ - feature flags, public URLs, modes, IDs, log levels, and similar values belong here
903
1668
 
904
- Never emit untyped ad hoc config objects when a normal Devflare config is appropriate.
1669
+ 5. **Use `secrets` only to declare secret bindings**
1670
+ - actual secret values must come from runtime secret sources, not from committed config
905
1671
 
906
- ### Prefer `ref()` for cross-worker relationships
1672
+ 6. **Prefer `ref()` for cross-worker composition**
1673
+ - do not duplicate worker names and DO script names manually if a config reference is available
907
1674
 
908
- Avoid manually duplicating worker names and binding metadata.
1675
+ 7. **Prefer `devflare/test` for tests**
1676
+ - use `createTestContext()` and `cf.*` for integration-style tests
1677
+ - use mock helpers for pure unit tests
1678
+ - pass an explicit config path in monorepos or when using `.mts`/`.mjs` config files
909
1679
 
910
- ### Prefer `devflare/test` for tests
1680
+ 8. **Use imported `env` when it improves ergonomics**
1681
+ - especially in tests and shared helpers
911
1682
 
912
- Use `createTestContext()` for integration-style tests and mock helpers for unit tests.
1683
+ 9. **Respect remote-only boundaries**
1684
+ - do not pretend AI or Vectorize is “just local”
913
1685
 
914
- ### Prefer `env` for ergonomic binding access
1686
+ 10. **Use `wrangler.passthrough` instead of inventing unsupported native fields**
1687
+ - if you need a Wrangler option Devflare does not model yet, passthrough is the supported escape hatch
915
1688
 
916
- Use `import { env } from 'devflare'` when it improves clarity.
917
-
918
- ### Respect remote-only boundaries
919
-
920
- Do not fake fully local AI or Vectorize support.
921
-
922
- ### Use routing when HTTP complexity grows
923
-
924
- If the app has many endpoints, prefer `files.routes` and route files over a giant `fetch.ts` dispatcher.
1689
+ 11. **Do not assume every test helper drains `waitUntil()` the same way**
1690
+ - `cf.worker.fetch()` and queue/scheduled helpers have importantly different behavior
925
1691
 
926
1692
  ---
927
1693
 
@@ -932,7 +1698,10 @@ If the app has many endpoints, prefer `files.routes` and route files over a gian
932
1698
  import { defineConfig } from 'devflare'
933
1699
 
934
1700
  export default defineConfig({
935
- name: 'hello-worker'
1701
+ name: 'hello-worker',
1702
+ files: {
1703
+ fetch: 'src/fetch.ts'
1704
+ }
936
1705
  })
937
1706
  ```
938
1707
 
@@ -965,12 +1734,18 @@ describe('hello-worker', () => {
965
1734
 
966
1735
  ## What to remember
967
1736
 
968
- If you remember only seven things, remember these:
1737
+ If you only remember a few things, remember these:
969
1738
 
970
1739
  1. `defineConfig()` is the source of truth
971
- 2. Devflare extends Miniflare into a full developer workflow
972
- 3. file structure is a feature, not a side detail
973
- 4. responsibilities should live in separate surfaces and files
974
- 5. browser rendering is a concrete example of Devflare adding capabilities above raw Miniflare
975
- 6. `devflare/test` gives you a coherent test API across handler types
976
- 7. use routing, refs, and framework integrations when the app grows beyond a tiny single-worker shape
1740
+ 2. Devflare extends Miniflare into a broader developer workflow rather than replacing Cloudflare’s runtime model
1741
+ 3. Bun env-file loading and Devflare config loading are related but not the same thing
1742
+ 4. `vars` are built into generated config and are not secrets
1743
+ 5. `secrets` declare runtime secret bindings, default to `required: true`, and still need real runtime secret sources
1744
+ 6. `config.env` is a Devflare merge layer applied before compilation, and it supports only part of the top-level config surface
1745
+ 7. `config.env` uses `defu`, so arrays concatenate and nullish values do not delete base config
1746
+ 8. `wrangler.passthrough` is merged last and can override compiled fields
1747
+ 9. `files.routes`, top-level `routes`, and `wsRoutes` are three different concepts
1748
+ 10. `createTestContext()` is caller-relative and not every test helper behaves the same way around `waitUntil()`
1749
+ 11. `devflare/test` plus unified `env` is a major part of the package’s value
1750
+ 12. use `ref()` for multi-worker composition instead of magic strings
1751
+ 13. when native config does not cover a Wrangler feature, use `wrangler.passthrough`