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.
- package/LLM.md +1280 -505
- package/README.md +326 -458
- 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`
|
|
3
|
+
This file is for LLMs and developers **using** the published `devflare` package.
|
|
4
4
|
|
|
5
|
-
It
|
|
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
|
|
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
|
|
28
|
+
Devflare is a **developer platform layer for Cloudflare Workers**.
|
|
14
29
|
|
|
15
|
-
|
|
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
|
-
|
|
32
|
+
Devflare sits on top of those pieces and turns them into one coherent authoring model.
|
|
18
33
|
|
|
19
|
-
Devflare
|
|
34
|
+
Concretely, Devflare provides:
|
|
20
35
|
|
|
21
36
|
- typed config via `defineConfig()`
|
|
22
|
-
- convention-
|
|
23
|
-
-
|
|
24
|
-
- local Miniflare
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- framework integration for Vite and SvelteKit
|
|
28
|
-
- local
|
|
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
|
-
|
|
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
|
-
##
|
|
51
|
+
## The authoring model Devflare wants
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
Devflare is intentionally opinionated.
|
|
37
54
|
|
|
38
|
-
|
|
55
|
+
It wants you to think in **surfaces** rather than “one giant worker file that does everything”.
|
|
39
56
|
|
|
40
|
-
|
|
57
|
+
Typical surfaces are:
|
|
41
58
|
|
|
42
|
-
|
|
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
|
-
|
|
69
|
+
This separation is one of Devflare’s biggest advantages:
|
|
45
70
|
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
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
|
-
|
|
76
|
+
When generating or reviewing Devflare code, prefer **one responsibility per file** unless there is a very strong reason not to.
|
|
54
77
|
|
|
55
|
-
|
|
78
|
+
---
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
## Package entrypoints
|
|
58
81
|
|
|
59
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
+
- `defineConfig`
|
|
105
|
+
- `loadConfig`
|
|
106
|
+
- `compileConfig`
|
|
107
|
+
- `stringifyConfig`
|
|
108
|
+
- `ref`
|
|
109
|
+
- `env`
|
|
110
|
+
- `workerName`
|
|
93
111
|
|
|
94
|
-
The
|
|
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
|
-
|
|
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
|
-
|
|
119
|
+
### Deprecated and compatibility exports
|
|
101
120
|
|
|
102
|
-
|
|
121
|
+
The package still exposes a few compatibility surfaces so older code keeps working.
|
|
103
122
|
|
|
104
|
-
|
|
123
|
+
Prefer the newer APIs in fresh code:
|
|
105
124
|
|
|
106
|
-
|
|
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
|
-
|
|
131
|
+
---
|
|
109
132
|
|
|
110
|
-
|
|
133
|
+
## Config lifecycle
|
|
111
134
|
|
|
112
|
-
|
|
135
|
+
This is the actual config lifecycle Devflare uses.
|
|
113
136
|
|
|
114
|
-
|
|
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
|
-
|
|
144
|
+
### Config file names
|
|
117
145
|
|
|
118
|
-
|
|
146
|
+
`loadConfig()` looks for these filenames, in this order:
|
|
119
147
|
|
|
120
|
-
|
|
148
|
+
- `devflare.config.ts`
|
|
149
|
+
- `devflare.config.mts`
|
|
150
|
+
- `devflare.config.js`
|
|
151
|
+
- `devflare.config.mjs`
|
|
121
152
|
|
|
122
|
-
###
|
|
153
|
+
### `defineConfig()` supported forms
|
|
123
154
|
|
|
124
|
-
|
|
155
|
+
`defineConfig()` accepts all of these forms:
|
|
125
156
|
|
|
126
|
-
|
|
157
|
+
```ts
|
|
158
|
+
import { defineConfig } from 'devflare'
|
|
127
159
|
|
|
128
|
-
|
|
160
|
+
export default defineConfig({
|
|
161
|
+
name: 'my-worker'
|
|
162
|
+
})
|
|
163
|
+
```
|
|
129
164
|
|
|
130
|
-
|
|
165
|
+
```ts
|
|
166
|
+
import { defineConfig } from 'devflare'
|
|
131
167
|
|
|
132
|
-
|
|
168
|
+
export default defineConfig(() => ({
|
|
169
|
+
name: process.env.WORKER_NAME ?? 'my-worker'
|
|
170
|
+
}))
|
|
171
|
+
```
|
|
133
172
|
|
|
134
|
-
|
|
173
|
+
```ts
|
|
174
|
+
import { defineConfig } from 'devflare'
|
|
175
|
+
|
|
176
|
+
export default defineConfig(async () => ({
|
|
177
|
+
name: 'my-worker'
|
|
178
|
+
}))
|
|
179
|
+
```
|
|
135
180
|
|
|
136
|
-
|
|
181
|
+
It also supports a generic entrypoint type parameter:
|
|
137
182
|
|
|
138
|
-
|
|
183
|
+
```ts
|
|
184
|
+
import { defineConfig } from 'devflare'
|
|
139
185
|
|
|
140
|
-
|
|
186
|
+
export default defineConfig<import('./env').Entrypoints>({
|
|
187
|
+
name: 'my-worker'
|
|
188
|
+
})
|
|
189
|
+
```
|
|
141
190
|
|
|
142
|
-
|
|
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
|
-
##
|
|
195
|
+
## How env files, `vars`, and `secrets` actually work
|
|
147
196
|
|
|
148
|
-
|
|
197
|
+
This is the section people most often get wrong.
|
|
149
198
|
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
217
|
+
### What Devflare itself does and does not load
|
|
165
218
|
|
|
166
|
-
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
+
### Why `vars` are not secrets
|
|
187
272
|
|
|
188
|
-
|
|
273
|
+
Devflare’s compiler writes `vars` into the generated Wrangler config object.
|
|
189
274
|
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
+
### What `secrets` actually mean in Devflare
|
|
202
283
|
|
|
203
|
-
|
|
284
|
+
The current `secrets` field is a declaration layer. It tells Devflare which secret names exist and whether they are required.
|
|
204
285
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
Use:
|
|
286
|
+
Today, each secret is configured as:
|
|
208
287
|
|
|
209
288
|
```ts
|
|
210
|
-
|
|
289
|
+
secrets: {
|
|
290
|
+
MY_SECRET: { required: true }
|
|
291
|
+
}
|
|
211
292
|
```
|
|
212
293
|
|
|
213
|
-
|
|
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
|
-
|
|
300
|
+
What it **does** mean:
|
|
218
301
|
|
|
219
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
308
|
+
The filename `.env` can be confusing because it may participate in **two different stories**:
|
|
230
309
|
|
|
231
|
-
|
|
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
|
-
|
|
313
|
+
So the same file can accidentally influence both config-time and runtime behavior.
|
|
234
314
|
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
##
|
|
415
|
+
## `files`: conventions, discovery, and what each tool does with them
|
|
304
416
|
|
|
305
|
-
Devflare
|
|
417
|
+
The `files` section tells Devflare where to find handlers and discovery targets.
|
|
306
418
|
|
|
307
|
-
|
|
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
|
-
|
|
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
|
-
|
|
498
|
+
Typical route-tree mental model:
|
|
326
499
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
500
|
+
```text
|
|
501
|
+
src/routes/
|
|
502
|
+
├── index.ts
|
|
503
|
+
├── users/
|
|
504
|
+
│ ├── index.ts
|
|
505
|
+
│ └── [id].ts
|
|
506
|
+
└── health.ts
|
|
507
|
+
```
|
|
332
508
|
|
|
333
|
-
|
|
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
|
-
##
|
|
513
|
+
## `bindings`: supported binding types and what they mean
|
|
338
514
|
|
|
339
|
-
|
|
515
|
+
Bindings are the heart of the runtime contract.
|
|
340
516
|
|
|
341
|
-
|
|
342
|
-
- consume them in `src/queue.ts`
|
|
343
|
-
- test them with `cf.queue`
|
|
517
|
+
### Binding reference
|
|
344
518
|
|
|
345
|
-
|
|
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
|
-
|
|
348
|
-
import { defineConfig } from 'devflare'
|
|
535
|
+
### Core storage bindings
|
|
349
536
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
560
|
+
bindings: {
|
|
561
|
+
durableObjects: {
|
|
562
|
+
COUNTER: 'Counter'
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
376
566
|
|
|
377
|
-
|
|
378
|
-
const url = new URL(request.url)
|
|
567
|
+
Explicit form:
|
|
379
568
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
579
|
+
Cross-worker form uses `scriptName` or a `ref()`-produced DO binding.
|
|
395
580
|
|
|
396
|
-
|
|
397
|
-
|
|
581
|
+
### Queues
|
|
582
|
+
|
|
583
|
+
Producers and consumers are separated intentionally.
|
|
398
584
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
625
|
+
Current compiler nuance:
|
|
423
626
|
|
|
424
|
-
-
|
|
425
|
-
-
|
|
426
|
-
-
|
|
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
|
-
|
|
638
|
+
const auth = ref(() => import('../auth/devflare.config'))
|
|
432
639
|
|
|
433
|
-
|
|
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
|
-
###
|
|
651
|
+
### Browser rendering
|
|
436
652
|
|
|
437
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
665
|
+
### AI and Vectorize
|
|
465
666
|
|
|
466
|
-
|
|
667
|
+
These services are fundamentally remote-oriented.
|
|
467
668
|
|
|
468
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
474
|
-
import { defineConfig } from 'devflare'
|
|
673
|
+
### `sendEmail`
|
|
475
674
|
|
|
476
|
-
|
|
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
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
496
|
-
- `env.EMAIL: SendEmail` — recipient provided at send time
|
|
691
|
+
## `vars`, `secrets`, and generated types
|
|
497
692
|
|
|
498
|
-
###
|
|
693
|
+
### `vars`
|
|
499
694
|
|
|
500
|
-
|
|
695
|
+
Current Devflare schema models `vars` as:
|
|
501
696
|
|
|
502
697
|
```ts
|
|
503
|
-
|
|
698
|
+
Record<string, string>
|
|
699
|
+
```
|
|
504
700
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
723
|
+
```ts
|
|
724
|
+
secrets: {
|
|
725
|
+
API_KEY: {}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
527
728
|
|
|
528
|
-
|
|
729
|
+
means “`API_KEY` is a required runtime secret”, not “optional secret with no requirements”.
|
|
529
730
|
|
|
530
|
-
|
|
731
|
+
### `devflare types`
|
|
531
732
|
|
|
532
|
-
|
|
733
|
+
`devflare types` generates `env.d.ts` and uses config/discovery information to generate:
|
|
533
734
|
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
744
|
+
```text
|
|
745
|
+
bunx --bun devflare types
|
|
746
|
+
```
|
|
541
747
|
|
|
542
|
-
|
|
748
|
+
After generation, project code can use the global `DevflareEnv` interface and typed `ref()` entrypoints.
|
|
543
749
|
|
|
544
750
|
---
|
|
545
751
|
|
|
546
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
767
|
+
That means omitted nested fields inherit from the base config.
|
|
551
768
|
|
|
552
|
-
|
|
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
|
-
|
|
556
|
-
durableObjects: {
|
|
557
|
-
COUNTER: 'Counter'
|
|
558
|
-
}
|
|
559
|
-
}
|
|
802
|
+
defu(config.env[environment], config)
|
|
560
803
|
```
|
|
561
804
|
|
|
562
|
-
|
|
805
|
+
That means all of the following are true:
|
|
563
806
|
|
|
564
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
576
|
-
|
|
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
|
-
|
|
583
|
-
// src/do.session.ts
|
|
584
|
-
import { DurableObject } from 'cloudflare:workers'
|
|
849
|
+
Selecting `dev` gives you a merged result equivalent to:
|
|
585
850
|
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
594
|
-
|
|
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
|
-
|
|
598
|
-
|
|
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
|
-
|
|
604
|
-
// src/fetch.ts
|
|
605
|
-
import { env } from 'devflare'
|
|
903
|
+
These are about **which hostnames/paths invoke the worker in deployment**.
|
|
606
904
|
|
|
607
|
-
|
|
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
|
-
|
|
612
|
-
const session = env.SESSION.get(id)
|
|
907
|
+
### `wsRoutes`
|
|
613
908
|
|
|
614
|
-
|
|
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
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
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
|
-
|
|
937
|
+
The native modeled shape is currently focused on `directory` plus optional binding access.
|
|
637
938
|
|
|
638
|
-
-
|
|
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
|
-
###
|
|
941
|
+
### `observability`
|
|
645
942
|
|
|
646
|
-
|
|
943
|
+
Current native shape:
|
|
647
944
|
|
|
648
945
|
```ts
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
664
|
-
// src/transport.ts
|
|
665
|
-
import { DoubleableNumber } from './DoubleableNumber'
|
|
952
|
+
This compiles directly.
|
|
666
953
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
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
|
-
|
|
680
|
-
private count = 0
|
|
966
|
+
### `migrations`
|
|
681
967
|
|
|
682
|
-
|
|
683
|
-
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
})
|
|
713
|
-
```
|
|
999
|
+
## `build` and `plugins`
|
|
1000
|
+
|
|
1001
|
+
Devflare’s schema includes:
|
|
714
1002
|
|
|
715
|
-
|
|
1003
|
+
- `build`
|
|
1004
|
+
- `plugins`
|
|
716
1005
|
|
|
717
|
-
|
|
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
|
-
|
|
1008
|
+
- `target`
|
|
1009
|
+
- `minify`
|
|
1010
|
+
- `sourcemap`
|
|
1011
|
+
- `rolldownOptions`
|
|
724
1012
|
|
|
725
|
-
|
|
1013
|
+
However, it is important to understand the current maturity of these fields:
|
|
726
1014
|
|
|
727
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
##
|
|
1080
|
+
## Unified `env`
|
|
749
1081
|
|
|
750
|
-
|
|
1082
|
+
Importing `env` from the main package gives you a **unified environment proxy**.
|
|
751
1083
|
|
|
752
|
-
|
|
1084
|
+
```ts
|
|
1085
|
+
import { env } from 'devflare'
|
|
1086
|
+
```
|
|
753
1087
|
|
|
754
|
-
|
|
755
|
-
- `resolve()`
|
|
756
|
-
- `pipe()`
|
|
757
|
-
- context-related utilities
|
|
1088
|
+
Current resolution order is:
|
|
758
1089
|
|
|
759
|
-
|
|
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
|
-
|
|
1096
|
+
- real request handling
|
|
1097
|
+
- test contexts created by `devflare/test`
|
|
1098
|
+
- bridge-backed local flows outside the request callback itself
|
|
764
1099
|
|
|
765
|
-
|
|
1100
|
+
### What `env.dispose()` is for
|
|
766
1101
|
|
|
767
|
-
|
|
1102
|
+
`env.dispose()` is primarily a **test cleanup hook**.
|
|
768
1103
|
|
|
769
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
1240
|
+
- `createMockTestContext`
|
|
1241
|
+
- `withTestContext`
|
|
1242
|
+
- mock binding factories such as `createMockKV` and `createMockD1`
|
|
790
1243
|
|
|
791
|
-
###
|
|
1244
|
+
### Skip helpers and remote-only tests
|
|
792
1245
|
|
|
793
|
-
Devflare
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
814
|
-
-
|
|
815
|
-
-
|
|
816
|
-
-
|
|
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
|
-
|
|
1430
|
+
This subpath exposes:
|
|
1431
|
+
|
|
1432
|
+
- `handle`
|
|
1433
|
+
- `createDevflarePlatform`
|
|
1434
|
+
- `createHandle`
|
|
1435
|
+
- `resetPlatform`
|
|
1436
|
+
- `resetConfigCache`
|
|
1437
|
+
- `isDevflareDev`
|
|
1438
|
+
- `getBridgePort`
|
|
821
1439
|
|
|
822
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
1473
|
+
## `devflare/cloudflare`
|
|
831
1474
|
|
|
832
|
-
|
|
1475
|
+
`devflare/cloudflare` is the Cloudflare-account helper module.
|
|
833
1476
|
|
|
834
|
-
|
|
1477
|
+
Its main export is the `account` helper object, which provides:
|
|
835
1478
|
|
|
836
|
-
-
|
|
837
|
-
-
|
|
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
|
-
|
|
1492
|
+
The effective account selection order is currently:
|
|
840
1493
|
|
|
841
|
-
|
|
1494
|
+
1. workspace account
|
|
1495
|
+
2. global default account
|
|
1496
|
+
3. primary Cloudflare account
|
|
842
1497
|
|
|
843
|
-
|
|
1498
|
+
### Where account preferences are stored
|
|
844
1499
|
|
|
845
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
1544
|
+
### Strong, well-defined areas
|
|
852
1545
|
|
|
853
|
-
|
|
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
|
|
1622
|
+
## Recommended project shape
|
|
858
1623
|
|
|
859
|
-
|
|
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
|
-
-
|
|
884
|
-
-
|
|
1646
|
+
- config is declarative
|
|
1647
|
+
- each runtime surface has a natural home
|
|
885
1648
|
- tests target named surfaces
|
|
886
|
-
-
|
|
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
|
|
893
|
-
|
|
894
|
-
### Prefer conventions first
|
|
1655
|
+
When generating Devflare code, follow these rules.
|
|
895
1656
|
|
|
896
|
-
|
|
1657
|
+
1. **Prefer `defineConfig()`**
|
|
1658
|
+
- do not invent ad hoc config shapes when a normal Devflare config is appropriate
|
|
897
1659
|
|
|
898
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1680
|
+
8. **Use imported `env` when it improves ergonomics**
|
|
1681
|
+
- especially in tests and shared helpers
|
|
911
1682
|
|
|
912
|
-
|
|
1683
|
+
9. **Respect remote-only boundaries**
|
|
1684
|
+
- do not pretend AI or Vectorize is “just local”
|
|
913
1685
|
|
|
914
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
972
|
-
3. file
|
|
973
|
-
4.
|
|
974
|
-
5.
|
|
975
|
-
6. `
|
|
976
|
-
7.
|
|
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`
|