devflare 1.0.0-next.0 → 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.
- package/LLM.md +1751 -0
- package/README.md +326 -458
- package/package.json +3 -2
package/LLM.md
ADDED
|
@@ -0,0 +1,1751 @@
|
|
|
1
|
+
# Devflare
|
|
2
|
+
|
|
3
|
+
This file is for LLMs and developers **using** the published `devflare` package.
|
|
4
|
+
|
|
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
|
+
|
|
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
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## What Devflare is
|
|
27
|
+
|
|
28
|
+
Devflare is a **developer platform layer for Cloudflare Workers**.
|
|
29
|
+
|
|
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.
|
|
31
|
+
|
|
32
|
+
Devflare sits on top of those pieces and turns them into one coherent authoring model.
|
|
33
|
+
|
|
34
|
+
Concretely, Devflare provides:
|
|
35
|
+
|
|
36
|
+
- typed config via `defineConfig()`
|
|
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
|
|
44
|
+
|
|
45
|
+
So Devflare is **not** “a different Workers runtime”.
|
|
46
|
+
|
|
47
|
+
It is a higher-level developer system that uses the Cloudflare ecosystem underneath.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## The authoring model Devflare wants
|
|
52
|
+
|
|
53
|
+
Devflare is intentionally opinionated.
|
|
54
|
+
|
|
55
|
+
It wants you to think in **surfaces** rather than “one giant worker file that does everything”.
|
|
56
|
+
|
|
57
|
+
Typical surfaces are:
|
|
58
|
+
|
|
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`
|
|
68
|
+
|
|
69
|
+
This separation is one of Devflare’s biggest advantages:
|
|
70
|
+
|
|
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
|
|
75
|
+
|
|
76
|
+
When generating or reviewing Devflare code, prefer **one responsibility per file** unless there is a very strong reason not to.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Package entrypoints
|
|
81
|
+
|
|
82
|
+
Use package subpaths intentionally.
|
|
83
|
+
|
|
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 |
|
|
93
|
+
|
|
94
|
+
### Important runtime note
|
|
95
|
+
|
|
96
|
+
`devflare/runtime` is intentionally **smaller** than the main package.
|
|
97
|
+
|
|
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`.
|
|
99
|
+
|
|
100
|
+
### Common main-package exports
|
|
101
|
+
|
|
102
|
+
The most common exports from `devflare` are:
|
|
103
|
+
|
|
104
|
+
- `defineConfig`
|
|
105
|
+
- `loadConfig`
|
|
106
|
+
- `compileConfig`
|
|
107
|
+
- `stringifyConfig`
|
|
108
|
+
- `ref`
|
|
109
|
+
- `env`
|
|
110
|
+
- `workerName`
|
|
111
|
+
|
|
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.
|
|
113
|
+
|
|
114
|
+
Two small but useful details:
|
|
115
|
+
|
|
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
|
|
118
|
+
|
|
119
|
+
### Deprecated and compatibility exports
|
|
120
|
+
|
|
121
|
+
The package still exposes a few compatibility surfaces so older code keeps working.
|
|
122
|
+
|
|
123
|
+
Prefer the newer APIs in fresh code:
|
|
124
|
+
|
|
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
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Config lifecycle
|
|
134
|
+
|
|
135
|
+
This is the actual config lifecycle Devflare uses.
|
|
136
|
+
|
|
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
|
|
143
|
+
|
|
144
|
+
### Config file names
|
|
145
|
+
|
|
146
|
+
`loadConfig()` looks for these filenames, in this order:
|
|
147
|
+
|
|
148
|
+
- `devflare.config.ts`
|
|
149
|
+
- `devflare.config.mts`
|
|
150
|
+
- `devflare.config.js`
|
|
151
|
+
- `devflare.config.mjs`
|
|
152
|
+
|
|
153
|
+
### `defineConfig()` supported forms
|
|
154
|
+
|
|
155
|
+
`defineConfig()` accepts all of these forms:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { defineConfig } from 'devflare'
|
|
159
|
+
|
|
160
|
+
export default defineConfig({
|
|
161
|
+
name: 'my-worker'
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { defineConfig } from 'devflare'
|
|
167
|
+
|
|
168
|
+
export default defineConfig(() => ({
|
|
169
|
+
name: process.env.WORKER_NAME ?? 'my-worker'
|
|
170
|
+
}))
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { defineConfig } from 'devflare'
|
|
175
|
+
|
|
176
|
+
export default defineConfig(async () => ({
|
|
177
|
+
name: 'my-worker'
|
|
178
|
+
}))
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
It also supports a generic entrypoint type parameter:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { defineConfig } from 'devflare'
|
|
185
|
+
|
|
186
|
+
export default defineConfig<import('./env').Entrypoints>({
|
|
187
|
+
name: 'my-worker'
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
That generic exists to improve type inference for `ref().worker('SomeEntrypoint')` after `env.d.ts` has been generated.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## How env files, `vars`, and `secrets` actually work
|
|
196
|
+
|
|
197
|
+
This is the section people most often get wrong.
|
|
198
|
+
|
|
199
|
+
### The short version
|
|
200
|
+
|
|
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
|
|
208
|
+
|
|
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 |
|
|
216
|
+
|
|
217
|
+
### What Devflare itself does and does not load
|
|
218
|
+
|
|
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
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { defineConfig } from 'devflare'
|
|
251
|
+
|
|
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
|
+
}))
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
What this means:
|
|
266
|
+
|
|
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`
|
|
270
|
+
|
|
271
|
+
### Why `vars` are not secrets
|
|
272
|
+
|
|
273
|
+
Devflare’s compiler writes `vars` into the generated Wrangler config object.
|
|
274
|
+
|
|
275
|
+
That means they are part of the deployment configuration shape. In other words:
|
|
276
|
+
|
|
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
|
|
281
|
+
|
|
282
|
+
### What `secrets` actually mean in Devflare
|
|
283
|
+
|
|
284
|
+
The current `secrets` field is a declaration layer. It tells Devflare which secret names exist and whether they are required.
|
|
285
|
+
|
|
286
|
+
Today, each secret is configured as:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
secrets: {
|
|
290
|
+
MY_SECRET: { required: true }
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
What this does **not** mean:
|
|
295
|
+
|
|
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
|
|
299
|
+
|
|
300
|
+
What it **does** mean:
|
|
301
|
+
|
|
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
|
|
305
|
+
|
|
306
|
+
### Local env-file overlap warning
|
|
307
|
+
|
|
308
|
+
The filename `.env` can be confusing because it may participate in **two different stories**:
|
|
309
|
+
|
|
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
|
|
312
|
+
|
|
313
|
+
So the same file can accidentally influence both config-time and runtime behavior.
|
|
314
|
+
|
|
315
|
+
If you want clearer separation, prefer this mental split:
|
|
316
|
+
|
|
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
|
|
319
|
+
|
|
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.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
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
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { defineConfig } from 'devflare'
|
|
356
|
+
|
|
357
|
+
export default defineConfig({
|
|
358
|
+
name: 'hello-worker'
|
|
359
|
+
})
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Realistic starting config
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
import { defineConfig } from 'devflare'
|
|
366
|
+
|
|
367
|
+
export default defineConfig({
|
|
368
|
+
name: 'my-app',
|
|
369
|
+
files: {
|
|
370
|
+
routes: {
|
|
371
|
+
dir: 'src/routes',
|
|
372
|
+
prefix: '/api'
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
bindings: {
|
|
376
|
+
kv: {
|
|
377
|
+
CACHE: 'cache-kv-id'
|
|
378
|
+
},
|
|
379
|
+
d1: {
|
|
380
|
+
DB: 'db-id'
|
|
381
|
+
},
|
|
382
|
+
r2: {
|
|
383
|
+
FILES: 'files-bucket'
|
|
384
|
+
},
|
|
385
|
+
durableObjects: {
|
|
386
|
+
COUNTER: 'Counter'
|
|
387
|
+
},
|
|
388
|
+
queues: {
|
|
389
|
+
producers: {
|
|
390
|
+
TASK_QUEUE: 'task-queue'
|
|
391
|
+
},
|
|
392
|
+
consumers: [
|
|
393
|
+
{
|
|
394
|
+
queue: 'task-queue',
|
|
395
|
+
maxBatchSize: 10,
|
|
396
|
+
maxRetries: 3
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
browser: {
|
|
401
|
+
binding: 'BROWSER'
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
triggers: {
|
|
405
|
+
crons: ['0 */6 * * *']
|
|
406
|
+
},
|
|
407
|
+
vars: {
|
|
408
|
+
LOG_LEVEL: 'info'
|
|
409
|
+
}
|
|
410
|
+
})
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## `files`: conventions, discovery, and what each tool does with them
|
|
416
|
+
|
|
417
|
+
The `files` section tells Devflare where to find handlers and discovery targets.
|
|
418
|
+
|
|
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 |
|
|
430
|
+
|
|
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
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
import { defineConfig } from 'devflare'
|
|
486
|
+
|
|
487
|
+
export default defineConfig({
|
|
488
|
+
name: 'my-api',
|
|
489
|
+
files: {
|
|
490
|
+
routes: {
|
|
491
|
+
dir: 'src/routes',
|
|
492
|
+
prefix: '/api'
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Typical route-tree mental model:
|
|
499
|
+
|
|
500
|
+
```text
|
|
501
|
+
src/routes/
|
|
502
|
+
├── index.ts
|
|
503
|
+
├── users/
|
|
504
|
+
│ ├── index.ts
|
|
505
|
+
│ └── [id].ts
|
|
506
|
+
└── health.ts
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Use `files.routes` when the HTTP surface is large enough that a single `fetch.ts` would become a switch-statement graveyard.
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## `bindings`: supported binding types and what they mean
|
|
514
|
+
|
|
515
|
+
Bindings are the heart of the runtime contract.
|
|
516
|
+
|
|
517
|
+
### Binding reference
|
|
518
|
+
|
|
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 |
|
|
534
|
+
|
|
535
|
+
### Core storage bindings
|
|
536
|
+
|
|
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'
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Durable Objects
|
|
554
|
+
|
|
555
|
+
Durable Object bindings support both shorthand and object form.
|
|
556
|
+
|
|
557
|
+
Shorthand local form:
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
bindings: {
|
|
561
|
+
durableObjects: {
|
|
562
|
+
COUNTER: 'Counter'
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Explicit form:
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
bindings: {
|
|
571
|
+
durableObjects: {
|
|
572
|
+
COUNTER: {
|
|
573
|
+
className: 'Counter'
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Cross-worker form uses `scriptName` or a `ref()`-produced DO binding.
|
|
580
|
+
|
|
581
|
+
### Queues
|
|
582
|
+
|
|
583
|
+
Producers and consumers are separated intentionally.
|
|
584
|
+
|
|
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
|
+
}
|
|
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
|
|
610
|
+
|
|
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'
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Current compiler nuance:
|
|
626
|
+
|
|
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
|
|
630
|
+
|
|
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'
|
|
637
|
+
|
|
638
|
+
const auth = ref(() => import('../auth/devflare.config'))
|
|
639
|
+
|
|
640
|
+
export default defineConfig({
|
|
641
|
+
name: 'gateway',
|
|
642
|
+
bindings: {
|
|
643
|
+
services: {
|
|
644
|
+
AUTH: auth.worker,
|
|
645
|
+
ADMIN: auth.worker('AdminEntrypoint')
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
})
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Browser rendering
|
|
652
|
+
|
|
653
|
+
Browser rendering is one of the clearest examples of Devflare adding meaningful orchestration on top of a local Workers runtime.
|
|
654
|
+
|
|
655
|
+
```ts
|
|
656
|
+
bindings: {
|
|
657
|
+
browser: {
|
|
658
|
+
binding: 'BROWSER'
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
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.
|
|
664
|
+
|
|
665
|
+
### AI and Vectorize
|
|
666
|
+
|
|
667
|
+
These services are fundamentally remote-oriented.
|
|
668
|
+
|
|
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.
|
|
670
|
+
|
|
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.
|
|
672
|
+
|
|
673
|
+
### `sendEmail`
|
|
674
|
+
|
|
675
|
+
`sendEmail` is present in the schema and examples, with this current shape:
|
|
676
|
+
|
|
677
|
+
```ts
|
|
678
|
+
bindings: {
|
|
679
|
+
sendEmail: {
|
|
680
|
+
ADMIN_EMAIL: {
|
|
681
|
+
destinationAddress: 'admin@example.com'
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
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
|
+
---
|
|
690
|
+
|
|
691
|
+
## `vars`, `secrets`, and generated types
|
|
692
|
+
|
|
693
|
+
### `vars`
|
|
694
|
+
|
|
695
|
+
Current Devflare schema models `vars` as:
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
Record<string, string>
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
That is stricter than the broadest possible Wrangler configuration story.
|
|
702
|
+
|
|
703
|
+
So in Devflare today:
|
|
704
|
+
|
|
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 }
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
That means the native configuration is about **declaring expected secret names and whether they are required**, not about storing the secret values themselves.
|
|
718
|
+
|
|
719
|
+
One subtle but important default: `required` defaults to `true`.
|
|
720
|
+
|
|
721
|
+
So this:
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
secrets: {
|
|
725
|
+
API_KEY: {}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
means “`API_KEY` is a required runtime secret”, not “optional secret with no requirements”.
|
|
730
|
+
|
|
731
|
+
### `devflare types`
|
|
732
|
+
|
|
733
|
+
`devflare types` generates `env.d.ts` and uses config/discovery information to generate:
|
|
734
|
+
|
|
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
|
|
741
|
+
|
|
742
|
+
Recommended invocation:
|
|
743
|
+
|
|
744
|
+
```text
|
|
745
|
+
bunx --bun devflare types
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
After generation, project code can use the global `DevflareEnv` interface and typed `ref()` entrypoints.
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
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:
|
|
761
|
+
|
|
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**
|
|
766
|
+
|
|
767
|
+
That means omitted nested fields inherit from the base config.
|
|
768
|
+
|
|
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:
|
|
800
|
+
|
|
801
|
+
```ts
|
|
802
|
+
defu(config.env[environment], config)
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
That means all of the following are true:
|
|
806
|
+
|
|
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
|
|
811
|
+
|
|
812
|
+
### Example
|
|
813
|
+
|
|
814
|
+
```ts
|
|
815
|
+
import { defineConfig } from 'devflare'
|
|
816
|
+
|
|
817
|
+
export default defineConfig({
|
|
818
|
+
name: 'app',
|
|
819
|
+
triggers: {
|
|
820
|
+
crons: ['0 0 * * *']
|
|
821
|
+
},
|
|
822
|
+
vars: {
|
|
823
|
+
API_URL: 'https://api.example.com',
|
|
824
|
+
DEBUG: 'false'
|
|
825
|
+
},
|
|
826
|
+
bindings: {
|
|
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
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
})
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
Selecting `dev` gives you a merged result equivalent to:
|
|
850
|
+
|
|
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 * * *']`
|
|
855
|
+
|
|
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.
|
|
857
|
+
|
|
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
|
|
889
|
+
|
|
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'
|
|
899
|
+
}
|
|
900
|
+
]
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
These are about **which hostnames/paths invoke the worker in deployment**.
|
|
904
|
+
|
|
905
|
+
They are not related to file-based app routing.
|
|
906
|
+
|
|
907
|
+
### `wsRoutes`
|
|
908
|
+
|
|
909
|
+
`wsRoutes` are for Devflare local development. They describe how websocket upgrade requests should be forwarded into Durable Objects during dev.
|
|
910
|
+
|
|
911
|
+
Current shape:
|
|
912
|
+
|
|
913
|
+
```ts
|
|
914
|
+
wsRoutes: [
|
|
915
|
+
{
|
|
916
|
+
pattern: '/chat/api',
|
|
917
|
+
doNamespace: 'CHAT_ROOM',
|
|
918
|
+
idParam: 'roomId',
|
|
919
|
+
forwardPath: '/websocket'
|
|
920
|
+
}
|
|
921
|
+
]
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
These are **not** compiled into Wrangler config.
|
|
925
|
+
|
|
926
|
+
### `assets`
|
|
927
|
+
|
|
928
|
+
Current native Devflare assets shape is:
|
|
929
|
+
|
|
930
|
+
```ts
|
|
931
|
+
assets: {
|
|
932
|
+
directory: './public',
|
|
933
|
+
binding: 'ASSETS'
|
|
934
|
+
}
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
The native modeled shape is currently focused on `directory` plus optional binding access.
|
|
938
|
+
|
|
939
|
+
If you need more advanced asset-related Wrangler fields, use `wrangler.passthrough`.
|
|
940
|
+
|
|
941
|
+
### `observability`
|
|
942
|
+
|
|
943
|
+
Current native shape:
|
|
944
|
+
|
|
945
|
+
```ts
|
|
946
|
+
observability: {
|
|
947
|
+
enabled: true,
|
|
948
|
+
head_sampling_rate: 0.1
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
This compiles directly.
|
|
953
|
+
|
|
954
|
+
### `limits`
|
|
955
|
+
|
|
956
|
+
Current native shape only models:
|
|
957
|
+
|
|
958
|
+
```ts
|
|
959
|
+
limits: {
|
|
960
|
+
cpu_ms: 50
|
|
961
|
+
}
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
If you need Wrangler/Cloudflare limit-related fields outside that native shape, use `wrangler.passthrough`.
|
|
965
|
+
|
|
966
|
+
### `migrations`
|
|
967
|
+
|
|
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`
|
|
979
|
+
|
|
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
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
The compiler merges these keys directly into the top level of the generated Wrangler config.
|
|
994
|
+
|
|
995
|
+
This is the correct escape hatch when native Devflare config is intentionally narrower than Wrangler’s full surface.
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
## `build` and `plugins`
|
|
1000
|
+
|
|
1001
|
+
Devflare’s schema includes:
|
|
1002
|
+
|
|
1003
|
+
- `build`
|
|
1004
|
+
- `plugins`
|
|
1005
|
+
|
|
1006
|
+
Current `build` shape includes:
|
|
1007
|
+
|
|
1008
|
+
- `target`
|
|
1009
|
+
- `minify`
|
|
1010
|
+
- `sourcemap`
|
|
1011
|
+
- `rolldownOptions`
|
|
1012
|
+
|
|
1013
|
+
However, it is important to understand the current maturity of these fields:
|
|
1014
|
+
|
|
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
|
|
1051
|
+
|
|
1052
|
+
```ts
|
|
1053
|
+
import { defineConfig, ref } from 'devflare'
|
|
1054
|
+
|
|
1055
|
+
const auth = ref(() => import('../auth/devflare.config'))
|
|
1056
|
+
|
|
1057
|
+
export default defineConfig({
|
|
1058
|
+
name: 'gateway',
|
|
1059
|
+
bindings: {
|
|
1060
|
+
services: {
|
|
1061
|
+
AUTH: auth.worker,
|
|
1062
|
+
ADMIN: auth.worker('AdminEntrypoint')
|
|
1063
|
+
},
|
|
1064
|
+
durableObjects: {
|
|
1065
|
+
AUTH_CACHE: auth.AUTH_CACHE
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
})
|
|
1069
|
+
```
|
|
1070
|
+
|
|
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
|
|
1077
|
+
|
|
1078
|
+
---
|
|
1079
|
+
|
|
1080
|
+
## Unified `env`
|
|
1081
|
+
|
|
1082
|
+
Importing `env` from the main package gives you a **unified environment proxy**.
|
|
1083
|
+
|
|
1084
|
+
```ts
|
|
1085
|
+
import { env } from 'devflare'
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
Current resolution order is:
|
|
1089
|
+
|
|
1090
|
+
1. request-scoped context
|
|
1091
|
+
2. test context
|
|
1092
|
+
3. bridge env
|
|
1093
|
+
|
|
1094
|
+
That means the same import can work across:
|
|
1095
|
+
|
|
1096
|
+
- real request handling
|
|
1097
|
+
- test contexts created by `devflare/test`
|
|
1098
|
+
- bridge-backed local flows outside the request callback itself
|
|
1099
|
+
|
|
1100
|
+
### What `env.dispose()` is for
|
|
1101
|
+
|
|
1102
|
+
`env.dispose()` is primarily a **test cleanup hook**.
|
|
1103
|
+
|
|
1104
|
+
Typical test pattern:
|
|
1105
|
+
|
|
1106
|
+
```ts
|
|
1107
|
+
import { beforeAll, afterAll, describe, expect, test } from 'bun:test'
|
|
1108
|
+
import { createTestContext, cf } from 'devflare/test'
|
|
1109
|
+
import { env } from 'devflare'
|
|
1110
|
+
|
|
1111
|
+
beforeAll(() => createTestContext())
|
|
1112
|
+
afterAll(() => env.dispose())
|
|
1113
|
+
|
|
1114
|
+
describe('worker', () => {
|
|
1115
|
+
test('GET /health', async () => {
|
|
1116
|
+
const response = await cf.worker.get('/health')
|
|
1117
|
+
expect(response.status).toBe(200)
|
|
1118
|
+
})
|
|
1119
|
+
})
|
|
1120
|
+
```
|
|
1121
|
+
|
|
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:
|
|
1239
|
+
|
|
1240
|
+
- `createMockTestContext`
|
|
1241
|
+
- `withTestContext`
|
|
1242
|
+
- mock binding factories such as `createMockKV` and `createMockD1`
|
|
1243
|
+
|
|
1244
|
+
### Skip helpers and remote-only tests
|
|
1245
|
+
|
|
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.
|
|
1247
|
+
|
|
1248
|
+
Important current behavior:
|
|
1249
|
+
|
|
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.
|
|
1261
|
+
|
|
1262
|
+
---
|
|
1263
|
+
|
|
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.
|
|
1337
|
+
|
|
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
|
|
1380
|
+
|
|
1381
|
+
### `devflare/vite`
|
|
1382
|
+
|
|
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:
|
|
1397
|
+
|
|
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
|
+
```
|
|
1427
|
+
|
|
1428
|
+
### `devflare/sveltekit`
|
|
1429
|
+
|
|
1430
|
+
This subpath exposes:
|
|
1431
|
+
|
|
1432
|
+
- `handle`
|
|
1433
|
+
- `createDevflarePlatform`
|
|
1434
|
+
- `createHandle`
|
|
1435
|
+
- `resetPlatform`
|
|
1436
|
+
- `resetConfigCache`
|
|
1437
|
+
- `isDevflareDev`
|
|
1438
|
+
- `getBridgePort`
|
|
1439
|
+
|
|
1440
|
+
Use it when SvelteKit should run against a Devflare-aware platform object and dev/runtime bridge.
|
|
1441
|
+
|
|
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
|
|
1470
|
+
|
|
1471
|
+
---
|
|
1472
|
+
|
|
1473
|
+
## `devflare/cloudflare`
|
|
1474
|
+
|
|
1475
|
+
`devflare/cloudflare` is the Cloudflare-account helper module.
|
|
1476
|
+
|
|
1477
|
+
Its main export is the `account` helper object, which provides:
|
|
1478
|
+
|
|
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
|
|
1491
|
+
|
|
1492
|
+
The effective account selection order is currently:
|
|
1493
|
+
|
|
1494
|
+
1. workspace account
|
|
1495
|
+
2. global default account
|
|
1496
|
+
3. primary Cloudflare account
|
|
1497
|
+
|
|
1498
|
+
### Where account preferences are stored
|
|
1499
|
+
|
|
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
|
|
1537
|
+
|
|
1538
|
+
---
|
|
1539
|
+
|
|
1540
|
+
## Current caveats and partial areas
|
|
1541
|
+
|
|
1542
|
+
This section exists on purpose. It is better to be accurate than accidentally over-promise.
|
|
1543
|
+
|
|
1544
|
+
### Strong, well-defined areas
|
|
1545
|
+
|
|
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.
|
|
1619
|
+
|
|
1620
|
+
---
|
|
1621
|
+
|
|
1622
|
+
## Recommended project shape
|
|
1623
|
+
|
|
1624
|
+
This is a strong default layout for many Devflare apps:
|
|
1625
|
+
|
|
1626
|
+
```text
|
|
1627
|
+
.
|
|
1628
|
+
├── devflare.config.ts
|
|
1629
|
+
├── env.d.ts
|
|
1630
|
+
├── src/
|
|
1631
|
+
│ ├── fetch.ts
|
|
1632
|
+
│ ├── queue.ts
|
|
1633
|
+
│ ├── scheduled.ts
|
|
1634
|
+
│ ├── email.ts
|
|
1635
|
+
│ ├── transport.ts
|
|
1636
|
+
│ ├── routes/
|
|
1637
|
+
│ ├── do.counter.ts
|
|
1638
|
+
│ ├── ep.admin.ts
|
|
1639
|
+
│ └── wf.sync.ts
|
|
1640
|
+
└── tests/
|
|
1641
|
+
└── worker.test.ts
|
|
1642
|
+
```
|
|
1643
|
+
|
|
1644
|
+
You will not always need every file, but this shape matches the Devflare mental model well:
|
|
1645
|
+
|
|
1646
|
+
- config is declarative
|
|
1647
|
+
- each runtime surface has a natural home
|
|
1648
|
+
- tests target named surfaces
|
|
1649
|
+
- multi-worker concerns can be expressed explicitly with `ref()`
|
|
1650
|
+
|
|
1651
|
+
---
|
|
1652
|
+
|
|
1653
|
+
## Guidance for LLMs generating Devflare code
|
|
1654
|
+
|
|
1655
|
+
When generating Devflare code, follow these rules.
|
|
1656
|
+
|
|
1657
|
+
1. **Prefer `defineConfig()`**
|
|
1658
|
+
- do not invent ad hoc config shapes when a normal Devflare config is appropriate
|
|
1659
|
+
|
|
1660
|
+
2. **Prefer separation of concerns**
|
|
1661
|
+
- do not merge fetch, queue, scheduled, email, DOs, routes, and entrypoints into one file unless explicitly asked
|
|
1662
|
+
|
|
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
|
|
1665
|
+
|
|
1666
|
+
4. **Use `vars` only for non-secret runtime configuration**
|
|
1667
|
+
- feature flags, public URLs, modes, IDs, log levels, and similar values belong here
|
|
1668
|
+
|
|
1669
|
+
5. **Use `secrets` only to declare secret bindings**
|
|
1670
|
+
- actual secret values must come from runtime secret sources, not from committed config
|
|
1671
|
+
|
|
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
|
|
1674
|
+
|
|
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
|
|
1679
|
+
|
|
1680
|
+
8. **Use imported `env` when it improves ergonomics**
|
|
1681
|
+
- especially in tests and shared helpers
|
|
1682
|
+
|
|
1683
|
+
9. **Respect remote-only boundaries**
|
|
1684
|
+
- do not pretend AI or Vectorize is “just local”
|
|
1685
|
+
|
|
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
|
|
1688
|
+
|
|
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
|
|
1691
|
+
|
|
1692
|
+
---
|
|
1693
|
+
|
|
1694
|
+
## Minimal example
|
|
1695
|
+
|
|
1696
|
+
```ts
|
|
1697
|
+
// devflare.config.ts
|
|
1698
|
+
import { defineConfig } from 'devflare'
|
|
1699
|
+
|
|
1700
|
+
export default defineConfig({
|
|
1701
|
+
name: 'hello-worker',
|
|
1702
|
+
files: {
|
|
1703
|
+
fetch: 'src/fetch.ts'
|
|
1704
|
+
}
|
|
1705
|
+
})
|
|
1706
|
+
```
|
|
1707
|
+
|
|
1708
|
+
```ts
|
|
1709
|
+
// src/fetch.ts
|
|
1710
|
+
export default async function fetch(): Promise<Response> {
|
|
1711
|
+
return new Response('Hello from Devflare')
|
|
1712
|
+
}
|
|
1713
|
+
```
|
|
1714
|
+
|
|
1715
|
+
```ts
|
|
1716
|
+
// tests/worker.test.ts
|
|
1717
|
+
import { beforeAll, afterAll, describe, expect, test } from 'bun:test'
|
|
1718
|
+
import { createTestContext, cf } from 'devflare/test'
|
|
1719
|
+
import { env } from 'devflare'
|
|
1720
|
+
|
|
1721
|
+
beforeAll(() => createTestContext())
|
|
1722
|
+
afterAll(() => env.dispose())
|
|
1723
|
+
|
|
1724
|
+
describe('hello-worker', () => {
|
|
1725
|
+
test('GET / returns text', async () => {
|
|
1726
|
+
const response = await cf.worker.get('/')
|
|
1727
|
+
expect(response.status).toBe(200)
|
|
1728
|
+
expect(await response.text()).toBe('Hello from Devflare')
|
|
1729
|
+
})
|
|
1730
|
+
})
|
|
1731
|
+
```
|
|
1732
|
+
|
|
1733
|
+
---
|
|
1734
|
+
|
|
1735
|
+
## What to remember
|
|
1736
|
+
|
|
1737
|
+
If you only remember a few things, remember these:
|
|
1738
|
+
|
|
1739
|
+
1. `defineConfig()` is the source of truth
|
|
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`
|