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/README.md
CHANGED
|
@@ -2,100 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
**Build Cloudflare Workers like an application, not a pile of glue.**
|
|
4
4
|
|
|
5
|
-
Devflare is a developer-first toolkit for Cloudflare Workers
|
|
5
|
+
Devflare is a developer-first toolkit for Cloudflare Workers built on top of Miniflare, Bun, Vite, and Wrangler-compatible configuration.
|
|
6
6
|
|
|
7
7
|
It gives you:
|
|
8
8
|
|
|
9
|
-
- a clean file structure
|
|
10
9
|
- typed config with `defineConfig()`
|
|
11
|
-
-
|
|
12
|
-
-
|
|
10
|
+
- convention-first file discovery
|
|
11
|
+
- local orchestration for multi-surface Worker apps
|
|
13
12
|
- first-class Durable Object and multi-worker workflows
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- testing APIs that mirror real handler surfaces
|
|
14
|
+
- browser-rendering support in local development
|
|
15
|
+
- Vite and SvelteKit integration
|
|
16
16
|
|
|
17
17
|
Miniflare gives you a local runtime.
|
|
18
18
|
|
|
19
19
|
Devflare turns that runtime into a **coherent development system**.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Why Devflare?
|
|
24
|
-
|
|
25
|
-
Cloudflare development gets messy fast.
|
|
26
|
-
|
|
27
|
-
You start with one Worker, then suddenly you have:
|
|
28
|
-
|
|
29
|
-
- HTTP routes
|
|
30
|
-
- queues
|
|
31
|
-
- scheduled jobs
|
|
32
|
-
- email handlers
|
|
33
|
-
- Durable Objects
|
|
34
|
-
- service bindings
|
|
35
|
-
- framework output
|
|
36
|
-
- test setup that no longer resembles runtime
|
|
37
|
-
|
|
38
|
-
Devflare keeps those responsibilities isolated and composable.
|
|
39
|
-
|
|
40
|
-
Instead of one giant worker file, you get a structure like this:
|
|
41
|
-
|
|
42
|
-
```text
|
|
43
|
-
.
|
|
44
|
-
├── devflare.config.ts
|
|
45
|
-
├── env.d.ts
|
|
46
|
-
├── src/
|
|
47
|
-
│ ├── fetch.ts
|
|
48
|
-
│ ├── queue.ts
|
|
49
|
-
│ ├── scheduled.ts
|
|
50
|
-
│ ├── email.ts
|
|
51
|
-
│ ├── routes/
|
|
52
|
-
│ ├── do.counter.ts
|
|
53
|
-
│ ├── ep.admin.ts
|
|
54
|
-
│ └── transport.ts
|
|
55
|
-
└── tests/
|
|
56
|
-
└── worker.test.ts
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
That structure is one of Devflare’s biggest strengths: **separate responsibilities, fewer surprises**.
|
|
21
|
+
For the exhaustive package-facing guide, including caveats and deeper config semantics, see [`LLM.md`](./LLM.md).
|
|
60
22
|
|
|
61
23
|
---
|
|
62
24
|
|
|
63
|
-
## What
|
|
25
|
+
## What Devflare adds
|
|
64
26
|
|
|
65
|
-
Devflare does not replace Miniflare.
|
|
27
|
+
Devflare does **not** replace Miniflare or Wrangler.
|
|
66
28
|
|
|
67
|
-
It
|
|
29
|
+
It adds the higher-level workflow around them:
|
|
68
30
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
- unified test helpers for `fetch`, `queue`, `scheduled`, `email`, and `tail`
|
|
75
|
-
- typed cross-worker references with `ref()`
|
|
76
|
-
- local orchestration for multi-surface Worker apps
|
|
77
|
-
- browser rendering support in local development
|
|
31
|
+
- convention-first file structure
|
|
32
|
+
- config validation and compilation
|
|
33
|
+
- typed cross-worker references via `ref()`
|
|
34
|
+
- unified testing helpers for `fetch`, `queue`, `scheduled`, `email`, and `tail`
|
|
35
|
+
- bridge-backed local development where needed
|
|
78
36
|
- framework-aware integration for Vite and SvelteKit
|
|
79
37
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
> Devflare helps you build Cloudflare systems with clear boundaries and smooth local development.
|
|
38
|
+
The result is a Worker app that stays structured as it grows instead of slowly becoming a single-file boss fight.
|
|
83
39
|
|
|
84
40
|
---
|
|
85
41
|
|
|
86
42
|
## Install
|
|
87
43
|
|
|
88
|
-
For
|
|
44
|
+
For the recommended full workflow, install Devflare together with Wrangler, Vite, and the Cloudflare Vite plugin:
|
|
89
45
|
|
|
90
46
|
```bash
|
|
91
|
-
bun add -d devflare wrangler
|
|
47
|
+
bun add -d devflare wrangler vite @cloudflare/vite-plugin
|
|
92
48
|
```
|
|
93
49
|
|
|
94
|
-
If you
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
bun add -d vite @cloudflare/vite-plugin
|
|
98
|
-
```
|
|
50
|
+
If you only need the library APIs and not the default Vite-based workflow, you can install fewer packages. The default CLI and diagnostics are built around the Vite + Cloudflare plugin setup.
|
|
99
51
|
|
|
100
52
|
---
|
|
101
53
|
|
|
@@ -108,7 +60,10 @@ bun add -d vite @cloudflare/vite-plugin
|
|
|
108
60
|
import { defineConfig } from 'devflare'
|
|
109
61
|
|
|
110
62
|
export default defineConfig({
|
|
111
|
-
name: 'hello-worker'
|
|
63
|
+
name: 'hello-worker',
|
|
64
|
+
files: {
|
|
65
|
+
fetch: 'src/fetch.ts'
|
|
66
|
+
}
|
|
112
67
|
})
|
|
113
68
|
```
|
|
114
69
|
|
|
@@ -127,7 +82,7 @@ export default async function fetch(): Promise<Response> {
|
|
|
127
82
|
bunx --bun devflare types
|
|
128
83
|
```
|
|
129
84
|
|
|
130
|
-
This
|
|
85
|
+
This generates `env.d.ts` so bindings, secrets, and discovered entrypoints stay typed.
|
|
131
86
|
|
|
132
87
|
### 4. Start development
|
|
133
88
|
|
|
@@ -157,540 +112,454 @@ describe('hello-worker', () => {
|
|
|
157
112
|
|
|
158
113
|
---
|
|
159
114
|
|
|
160
|
-
##
|
|
161
|
-
|
|
162
|
-
Devflare works best when each concern lives in its own file.
|
|
115
|
+
## Package entrypoints
|
|
163
116
|
|
|
164
|
-
|
|
117
|
+
Use subpaths intentionally.
|
|
165
118
|
|
|
166
|
-
|
|
|
119
|
+
| Import | Use for |
|
|
167
120
|
|---|---|
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `wf.*.ts` | workflows |
|
|
176
|
-
| `src/transport.ts` | custom serialization across boundaries |
|
|
121
|
+
| `devflare` | main package entrypoint: config helpers, `ref()`, unified `env`, CLI helpers, bridge helpers, decorators, test helpers, `workerName` |
|
|
122
|
+
| `devflare/runtime` | worker-safe middleware/validation/decorator utilities |
|
|
123
|
+
| `devflare/test` | `createTestContext`, `cf.*`, mock helpers, bridge test context, skip helpers |
|
|
124
|
+
| `devflare/vite` | Vite integration |
|
|
125
|
+
| `devflare/sveltekit` | SvelteKit integration |
|
|
126
|
+
| `devflare/cloudflare` | Cloudflare account/auth/usage/limits/preferences helpers |
|
|
127
|
+
| `devflare/decorators` | decorators only |
|
|
177
128
|
|
|
178
|
-
|
|
129
|
+
### Important runtime note
|
|
179
130
|
|
|
180
|
-
|
|
131
|
+
The published `devflare/runtime` subpath is intentionally smaller than the main package. If you want the unified `env` proxy, import it from `devflare`, not `devflare/runtime`.
|
|
181
132
|
|
|
182
133
|
---
|
|
183
134
|
|
|
184
|
-
##
|
|
135
|
+
## Config model
|
|
185
136
|
|
|
186
|
-
|
|
187
|
-
import { defineConfig } from 'devflare'
|
|
137
|
+
### `defineConfig()` is the source of truth
|
|
188
138
|
|
|
189
|
-
|
|
190
|
-
name: 'my-app',
|
|
191
|
-
files: {
|
|
192
|
-
routes: {
|
|
193
|
-
dir: 'src/routes',
|
|
194
|
-
prefix: '/api'
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
bindings: {
|
|
198
|
-
kv: {
|
|
199
|
-
CACHE: 'cache-kv-id'
|
|
200
|
-
},
|
|
201
|
-
d1: {
|
|
202
|
-
DB: 'db-id'
|
|
203
|
-
},
|
|
204
|
-
durableObjects: {
|
|
205
|
-
COUNTER: 'Counter'
|
|
206
|
-
},
|
|
207
|
-
queues: {
|
|
208
|
-
producers: {
|
|
209
|
-
TASK_QUEUE: 'task-queue'
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
browser: {
|
|
213
|
-
binding: 'BROWSER'
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
triggers: {
|
|
217
|
-
crons: ['0 */6 * * *']
|
|
218
|
-
}
|
|
219
|
-
})
|
|
220
|
-
```
|
|
139
|
+
Devflare works best when everything flows from `devflare.config.ts`.
|
|
221
140
|
|
|
222
|
-
|
|
141
|
+
`defineConfig()` supports:
|
|
223
142
|
|
|
224
|
-
|
|
143
|
+
- a plain object
|
|
144
|
+
- a sync function
|
|
145
|
+
- an async function
|
|
225
146
|
|
|
226
|
-
|
|
147
|
+
It also supports a generic parameter for generated entrypoint typing.
|
|
227
148
|
|
|
228
|
-
|
|
149
|
+
### Config file names
|
|
229
150
|
|
|
230
|
-
|
|
231
|
-
import { defineConfig } from 'devflare'
|
|
151
|
+
Devflare looks for these config filenames:
|
|
232
152
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
dir: 'src/routes',
|
|
238
|
-
prefix: '/api'
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
})
|
|
242
|
-
```
|
|
153
|
+
- `devflare.config.ts`
|
|
154
|
+
- `devflare.config.mts`
|
|
155
|
+
- `devflare.config.js`
|
|
156
|
+
- `devflare.config.mjs`
|
|
243
157
|
|
|
244
|
-
|
|
158
|
+
### Top-level config keys
|
|
159
|
+
|
|
160
|
+
These are the main config keys Devflare understands today.
|
|
161
|
+
|
|
162
|
+
| Key | What it means |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `name` | Worker name |
|
|
165
|
+
| `accountId` | Cloudflare account ID |
|
|
166
|
+
| `compatibilityDate` | Workers compatibility date |
|
|
167
|
+
| `compatibilityFlags` | Workers compatibility flags |
|
|
168
|
+
| `files` | handler paths and discovery globs |
|
|
169
|
+
| `bindings` | Cloudflare bindings |
|
|
170
|
+
| `triggers` | cron triggers |
|
|
171
|
+
| `vars` | non-secret runtime bindings |
|
|
172
|
+
| `secrets` | secret declarations |
|
|
173
|
+
| `routes` | deployment routes |
|
|
174
|
+
| `wsRoutes` | local websocket-to-DO proxy rules |
|
|
175
|
+
| `assets` | static assets config |
|
|
176
|
+
| `limits` | worker resource limits |
|
|
177
|
+
| `observability` | worker logs / sampling |
|
|
178
|
+
| `migrations` | Durable Object migrations |
|
|
179
|
+
| `build` | build-related metadata/options |
|
|
180
|
+
| `plugins` | plugin extension point |
|
|
181
|
+
| `env` | environment-specific overrides |
|
|
182
|
+
| `wrangler.passthrough` | escape hatch for Wrangler fields not modeled natively |
|
|
245
183
|
|
|
246
184
|
---
|
|
247
185
|
|
|
248
|
-
##
|
|
186
|
+
## Env files, `vars`, and `secrets`
|
|
249
187
|
|
|
250
|
-
|
|
188
|
+
This is the most important config nuance to understand.
|
|
251
189
|
|
|
252
|
-
|
|
253
|
-
- consume them in `src/queue.ts`
|
|
254
|
-
- test them with `cf.queue`
|
|
190
|
+
### The short version
|
|
255
191
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
192
|
+
- `loadConfig()` itself does **not** do dotenv loading
|
|
193
|
+
- the Devflare CLI runs under **Bun**, and Bun **does** auto-load `.env` files into `process.env`
|
|
194
|
+
- `vars` are plain config values compiled into generated Wrangler config
|
|
195
|
+
- `secrets` declare expected runtime secret bindings, but do **not** store secret values in config
|
|
259
196
|
|
|
260
|
-
|
|
261
|
-
name: 'tasks-app',
|
|
262
|
-
bindings: {
|
|
263
|
-
queues: {
|
|
264
|
-
producers: {
|
|
265
|
-
TASK_QUEUE: 'task-queue'
|
|
266
|
-
},
|
|
267
|
-
consumers: [
|
|
268
|
-
{
|
|
269
|
-
queue: 'task-queue',
|
|
270
|
-
maxBatchSize: 10,
|
|
271
|
-
maxRetries: 3
|
|
272
|
-
}
|
|
273
|
-
]
|
|
274
|
-
},
|
|
275
|
-
kv: {
|
|
276
|
-
RESULTS: 'results-kv-id'
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
```
|
|
197
|
+
### The layers to keep separate
|
|
281
198
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
199
|
+
| Thing | Used when | Good for secrets? |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `process.env` | config evaluation and build tooling | only if your own process handling is secure |
|
|
202
|
+
| `vars` | compile time and runtime | no |
|
|
203
|
+
| `secrets` | declaration + runtime binding expectations | yes |
|
|
204
|
+
| local runtime secret files like `.dev.vars` | local runtime | yes |
|
|
205
|
+
| Cloudflare secret store | deployed runtime | yes |
|
|
285
206
|
|
|
286
|
-
|
|
287
|
-
const url = new URL(request.url)
|
|
207
|
+
### What this means in practice
|
|
288
208
|
|
|
289
|
-
|
|
290
|
-
const task = {
|
|
291
|
-
id: crypto.randomUUID(),
|
|
292
|
-
type: 'resize-image'
|
|
293
|
-
}
|
|
209
|
+
If you run the CLI normally, Bun may populate `process.env` from `.env` before `devflare.config.ts` runs.
|
|
294
210
|
|
|
295
|
-
|
|
296
|
-
return Response.json({ queued: true, task })
|
|
297
|
-
}
|
|
211
|
+
That means:
|
|
298
212
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
```
|
|
213
|
+
- **CLI under Bun**: `.env` values are often available via `process.env`
|
|
214
|
+
- **programmatic `loadConfig()` elsewhere**: do not assume env files were loaded unless your process already loaded them
|
|
302
215
|
|
|
303
|
-
|
|
304
|
-
// src/queue.ts
|
|
305
|
-
import type { MessageBatch } from '@cloudflare/workers-types'
|
|
216
|
+
### `vars` vs `secrets`
|
|
306
217
|
|
|
307
|
-
|
|
308
|
-
id: string
|
|
309
|
-
type: string
|
|
310
|
-
}
|
|
218
|
+
Use `vars` for:
|
|
311
219
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
for (const message of batch.messages) {
|
|
317
|
-
try {
|
|
318
|
-
await env.RESULTS.put(
|
|
319
|
-
`result:${message.body.id}`,
|
|
320
|
-
JSON.stringify({ status: 'completed', type: message.body.type })
|
|
321
|
-
)
|
|
322
|
-
message.ack()
|
|
323
|
-
} catch {
|
|
324
|
-
message.retry()
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
```
|
|
220
|
+
- public base URLs
|
|
221
|
+
- feature flags
|
|
222
|
+
- log levels
|
|
223
|
+
- non-secret IDs and modes
|
|
329
224
|
|
|
330
|
-
|
|
225
|
+
Use `secrets` for:
|
|
331
226
|
|
|
332
|
-
|
|
227
|
+
- API keys
|
|
228
|
+
- tokens
|
|
229
|
+
- passwords
|
|
230
|
+
- signing secrets
|
|
333
231
|
|
|
334
|
-
Devflare
|
|
232
|
+
Current Devflare behavior matters here:
|
|
335
233
|
|
|
336
|
-
|
|
234
|
+
- `vars` are compiled into generated configuration
|
|
235
|
+
- `secrets` declare names and `required` status, but their values must come from a runtime secret source
|
|
337
236
|
|
|
338
|
-
|
|
237
|
+
### Example
|
|
339
238
|
|
|
340
239
|
```ts
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
to: message.to,
|
|
354
|
-
receivedAt: new Date().toISOString()
|
|
355
|
-
})
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
// Forward to an admin address
|
|
359
|
-
await message.forward(env.FORWARD_ADDRESS)
|
|
360
|
-
}
|
|
240
|
+
import { defineConfig } from 'devflare'
|
|
241
|
+
|
|
242
|
+
export default defineConfig(() => ({
|
|
243
|
+
name: 'billing-worker',
|
|
244
|
+
vars: {
|
|
245
|
+
PUBLIC_API_BASE: process.env.PUBLIC_API_BASE ?? 'http://localhost:5173',
|
|
246
|
+
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info'
|
|
247
|
+
},
|
|
248
|
+
secrets: {
|
|
249
|
+
STRIPE_SECRET_KEY: { required: true }
|
|
250
|
+
}
|
|
251
|
+
}))
|
|
361
252
|
```
|
|
362
253
|
|
|
363
|
-
|
|
254
|
+
---
|
|
364
255
|
|
|
365
|
-
|
|
256
|
+
## Environment overrides via `config.env`
|
|
366
257
|
|
|
367
|
-
|
|
258
|
+
`config.env` is a **Devflare merge layer**, not just a raw mirror of Wrangler environment blocks.
|
|
368
259
|
|
|
369
|
-
|
|
260
|
+
When you select an environment with `compileConfig(config, 'name')` or CLI `--env name`, Devflare:
|
|
370
261
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
262
|
+
1. starts from the base config
|
|
263
|
+
2. merges `config.env[name]` into it
|
|
264
|
+
3. compiles the resolved result
|
|
374
265
|
|
|
375
|
-
|
|
376
|
-
name: 'support-mail',
|
|
377
|
-
bindings: {
|
|
378
|
-
kv: {
|
|
379
|
-
EMAIL_LOG: 'email-log-kv-id'
|
|
380
|
-
},
|
|
381
|
-
sendEmail: {
|
|
382
|
-
// Locked to a specific recipient — every send goes to admin
|
|
383
|
-
ADMIN_EMAIL: { destinationAddress: 'admin@example.com' },
|
|
266
|
+
That means nested values can inherit cleanly from the base config.
|
|
384
267
|
|
|
385
|
-
|
|
386
|
-
EMAIL: {}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
})
|
|
390
|
-
```
|
|
268
|
+
This is one of the biggest behavioral differences from thinking in raw Wrangler-only terms.
|
|
391
269
|
|
|
392
|
-
|
|
270
|
+
---
|
|
393
271
|
|
|
394
|
-
|
|
395
|
-
- `env.EMAIL` — you provide the destination when composing the message
|
|
272
|
+
## File surfaces and routing
|
|
396
273
|
|
|
397
|
-
|
|
274
|
+
Devflare works best when each concern lives in its own file.
|
|
398
275
|
|
|
399
|
-
|
|
276
|
+
| File / pattern | Purpose |
|
|
277
|
+
|---|---|
|
|
278
|
+
| `src/fetch.ts` | HTTP requests |
|
|
279
|
+
| `src/queue.ts` | queue consumers |
|
|
280
|
+
| `src/scheduled.ts` | cron handlers |
|
|
281
|
+
| `src/email.ts` | incoming email handlers |
|
|
282
|
+
| `src/routes/**` | file-based routing |
|
|
283
|
+
| `do.*.ts` | Durable Objects |
|
|
284
|
+
| `ep.*.ts` | WorkerEntrypoints |
|
|
285
|
+
| `wf.*.ts` | workflows |
|
|
286
|
+
| `src/transport.ts` | custom serialization across boundaries |
|
|
400
287
|
|
|
401
|
-
|
|
402
|
-
import { email } from 'devflare/test'
|
|
288
|
+
### Do not confuse these three
|
|
403
289
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
})
|
|
290
|
+
| Key | Meaning |
|
|
291
|
+
|---|---|
|
|
292
|
+
| `files.routes` | file-based HTTP routing inside your app |
|
|
293
|
+
| top-level `routes` | Cloudflare deployment routing |
|
|
294
|
+
| `wsRoutes` | local websocket proxying into Durable Objects during dev |
|
|
410
295
|
|
|
411
|
-
|
|
412
|
-
```
|
|
296
|
+
Use `files.routes` when the HTTP surface is growing beyond a small `fetch.ts` handler.
|
|
413
297
|
|
|
414
298
|
---
|
|
415
299
|
|
|
416
|
-
##
|
|
300
|
+
## Bindings and multi-worker systems
|
|
417
301
|
|
|
418
|
-
Devflare
|
|
302
|
+
Devflare natively models these binding categories:
|
|
419
303
|
|
|
420
|
-
|
|
304
|
+
- KV
|
|
305
|
+
- D1
|
|
306
|
+
- R2
|
|
307
|
+
- Durable Objects
|
|
308
|
+
- Queues
|
|
309
|
+
- Services
|
|
310
|
+
- AI
|
|
311
|
+
- Vectorize
|
|
312
|
+
- Hyperdrive
|
|
313
|
+
- Browser Rendering
|
|
314
|
+
- Analytics Engine
|
|
315
|
+
- `sendEmail`
|
|
316
|
+
|
|
317
|
+
### Core examples
|
|
421
318
|
|
|
422
319
|
```ts
|
|
423
320
|
bindings: {
|
|
321
|
+
kv: {
|
|
322
|
+
CACHE: 'cache-kv-id'
|
|
323
|
+
},
|
|
324
|
+
d1: {
|
|
325
|
+
DB: 'db-id'
|
|
326
|
+
},
|
|
424
327
|
durableObjects: {
|
|
425
328
|
COUNTER: 'Counter'
|
|
329
|
+
},
|
|
330
|
+
queues: {
|
|
331
|
+
producers: {
|
|
332
|
+
TASK_QUEUE: 'task-queue'
|
|
333
|
+
}
|
|
426
334
|
}
|
|
427
335
|
}
|
|
428
336
|
```
|
|
429
337
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
### Write a Durable Object and call it from `fetch`
|
|
338
|
+
### Cross-worker references with `ref()`
|
|
433
339
|
|
|
434
|
-
|
|
340
|
+
Use `ref()` instead of hardcoding worker names and DO script names.
|
|
435
341
|
|
|
436
342
|
```ts
|
|
437
|
-
|
|
438
|
-
|
|
343
|
+
import { defineConfig, ref } from 'devflare'
|
|
344
|
+
|
|
345
|
+
const auth = ref(() => import('../auth/devflare.config'))
|
|
439
346
|
|
|
440
347
|
export default defineConfig({
|
|
441
|
-
name: '
|
|
348
|
+
name: 'gateway',
|
|
442
349
|
bindings: {
|
|
350
|
+
services: {
|
|
351
|
+
AUTH: auth.worker,
|
|
352
|
+
ADMIN: auth.worker('AdminEntrypoint')
|
|
353
|
+
},
|
|
443
354
|
durableObjects: {
|
|
444
|
-
|
|
355
|
+
AUTH_CACHE: auth.AUTH_CACHE
|
|
445
356
|
}
|
|
446
357
|
}
|
|
447
358
|
})
|
|
448
359
|
```
|
|
449
360
|
|
|
450
|
-
|
|
451
|
-
// src/do.session.ts
|
|
452
|
-
import { DurableObject } from 'cloudflare:workers'
|
|
361
|
+
This keeps multi-worker systems typed and centralized.
|
|
453
362
|
|
|
454
|
-
|
|
455
|
-
private data = new Map<string, string>()
|
|
363
|
+
---
|
|
456
364
|
|
|
457
|
-
|
|
458
|
-
return this.data.get(key) ?? null
|
|
459
|
-
}
|
|
365
|
+
## Testing and remote testing
|
|
460
366
|
|
|
461
|
-
|
|
462
|
-
this.data.set(key, value)
|
|
463
|
-
}
|
|
367
|
+
### `devflare/test`
|
|
464
368
|
|
|
465
|
-
|
|
466
|
-
this.data.clear()
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
```
|
|
369
|
+
The main testing entrypoint exports:
|
|
470
370
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
371
|
+
- `createTestContext`
|
|
372
|
+
- `cf`
|
|
373
|
+
- `email`
|
|
374
|
+
- `queue`
|
|
375
|
+
- `scheduled`
|
|
376
|
+
- `worker`
|
|
377
|
+
- `tail`
|
|
378
|
+
- mock helpers like `createMockKV`, `createMockD1`, `createMockR2`, `createMockQueue`
|
|
379
|
+
- bridge test helpers
|
|
380
|
+
- skip helpers like `shouldSkip`
|
|
474
381
|
|
|
475
|
-
|
|
476
|
-
const url = new URL(request.url)
|
|
477
|
-
const sessionId = url.searchParams.get('session') ?? 'default'
|
|
382
|
+
### Integration-style local tests
|
|
478
383
|
|
|
479
|
-
|
|
480
|
-
const session = env.SESSION.get(id)
|
|
384
|
+
Use `createTestContext()` when you want a real local environment backed by Devflare’s orchestration.
|
|
481
385
|
|
|
482
|
-
|
|
483
|
-
const key = url.searchParams.get('key') ?? 'name'
|
|
484
|
-
const value = url.searchParams.get('value') ?? 'Arthur'
|
|
485
|
-
await session.setValue(key, value)
|
|
486
|
-
return Response.json({ ok: true })
|
|
487
|
-
}
|
|
386
|
+
`cf` gives you a unified test surface:
|
|
488
387
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
388
|
+
- `cf.worker`
|
|
389
|
+
- `cf.queue`
|
|
390
|
+
- `cf.scheduled`
|
|
391
|
+
- `cf.email`
|
|
392
|
+
- `cf.tail`
|
|
494
393
|
|
|
495
|
-
|
|
496
|
-
await session.clearAll()
|
|
497
|
-
return Response.json({ ok: true })
|
|
498
|
-
}
|
|
394
|
+
### Remote-only services
|
|
499
395
|
|
|
500
|
-
|
|
501
|
-
|
|
396
|
+
AI and Vectorize are remote-only in practice. Devflare does not pretend they are fully local equivalents of KV or D1.
|
|
397
|
+
|
|
398
|
+
Use remote mode when you want to run tests against real Cloudflare infrastructure:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
bunx --bun devflare remote status
|
|
402
|
+
bunx --bun devflare remote enable 30
|
|
403
|
+
bunx --bun devflare remote disable
|
|
502
404
|
```
|
|
503
405
|
|
|
504
|
-
|
|
406
|
+
Remote mode can also be enabled with:
|
|
505
407
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
4. call your methods from `fetch`
|
|
408
|
+
```text
|
|
409
|
+
DEVFLARE_REMOTE=1
|
|
410
|
+
```
|
|
510
411
|
|
|
511
|
-
|
|
412
|
+
Remote mode is stored locally when enabled through the CLI and is intended to be temporary because it may incur real costs.
|
|
512
413
|
|
|
513
|
-
|
|
414
|
+
### Skip helpers
|
|
514
415
|
|
|
515
|
-
|
|
516
|
-
import { defineConfig, ref } from 'devflare'
|
|
416
|
+
Use `shouldSkip` to gate cost-sensitive or remote-only tests:
|
|
517
417
|
|
|
518
|
-
|
|
418
|
+
```ts
|
|
419
|
+
import { shouldSkip } from 'devflare/test'
|
|
519
420
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
bindings: {
|
|
523
|
-
services: {
|
|
524
|
-
AUTH: authWorker.worker
|
|
525
|
-
}
|
|
526
|
-
}
|
|
421
|
+
describe.skipIf(await shouldSkip.ai)('AI tests', () => {
|
|
422
|
+
// ...
|
|
527
423
|
})
|
|
528
424
|
```
|
|
529
425
|
|
|
530
|
-
|
|
426
|
+
The skip helpers check remote mode, authentication, account availability, and usage limits.
|
|
531
427
|
|
|
532
428
|
---
|
|
533
429
|
|
|
534
|
-
##
|
|
535
|
-
|
|
536
|
-
Sometimes your Worker or Durable Object wants to return a real class instance, not just plain JSON.
|
|
537
|
-
|
|
538
|
-
That is what `src/transport.ts` is for.
|
|
430
|
+
## CLI
|
|
539
431
|
|
|
540
|
-
|
|
432
|
+
The Devflare CLI covers the full development loop.
|
|
541
433
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
434
|
+
| Command | What it does |
|
|
435
|
+
|---|---|
|
|
436
|
+
| `devflare init` | scaffold a project |
|
|
437
|
+
| `devflare dev` | start the unified local development server |
|
|
438
|
+
| `devflare build` | resolve config, write `wrangler.jsonc`, run `vite build` |
|
|
439
|
+
| `devflare deploy` | build and deploy with Wrangler |
|
|
440
|
+
| `devflare types` | generate `env.d.ts` |
|
|
441
|
+
| `devflare doctor` | check project configuration and common workflow prerequisites |
|
|
442
|
+
| `devflare account` | inspect accounts, resources, usage, and limits |
|
|
443
|
+
| `devflare ai` | show Workers AI model pricing info |
|
|
444
|
+
| `devflare remote` | manage remote test mode |
|
|
546
445
|
|
|
547
|
-
|
|
548
|
-
this.value = n
|
|
549
|
-
}
|
|
446
|
+
### Useful CLI notes
|
|
550
447
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
```
|
|
448
|
+
- `build` and `deploy` accept `--env <name>` to select `config.env[name]`
|
|
449
|
+
- `doctor` assumes the default Vite-based Devflare workflow and checks for config, package metadata, Vite config, TypeScript config, and generated Wrangler output
|
|
450
|
+
- `account` includes subcommands like `workers`, `kv`, `d1`, `r2`, `vectorize`, `limits`, `usage`, `global`, and `workspace`
|
|
556
451
|
|
|
557
|
-
|
|
558
|
-
// src/transport.ts
|
|
559
|
-
import { DoubleableNumber } from './DoubleableNumber'
|
|
452
|
+
Recommended invocation style:
|
|
560
453
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
}
|
|
454
|
+
```bash
|
|
455
|
+
bunx --bun devflare dev
|
|
456
|
+
bunx --bun devflare types
|
|
457
|
+
bunx --bun devflare build
|
|
567
458
|
```
|
|
568
459
|
|
|
569
|
-
|
|
570
|
-
// src/do.counter.ts
|
|
571
|
-
import { DoubleableNumber } from './DoubleableNumber'
|
|
460
|
+
---
|
|
572
461
|
|
|
573
|
-
|
|
574
|
-
private count = 0
|
|
462
|
+
## `devflare/cloudflare`
|
|
575
463
|
|
|
576
|
-
|
|
577
|
-
this.count += n
|
|
578
|
-
return new DoubleableNumber(this.count)
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
```
|
|
464
|
+
`devflare/cloudflare` exposes the account helper API for Cloudflare-aware tooling.
|
|
582
465
|
|
|
583
466
|
```ts
|
|
584
|
-
|
|
585
|
-
import { beforeAll, afterAll, expect, test } from 'bun:test'
|
|
586
|
-
import { createTestContext } from 'devflare/test'
|
|
587
|
-
import { env } from 'devflare'
|
|
588
|
-
import { DoubleableNumber } from '../src/DoubleableNumber'
|
|
589
|
-
|
|
590
|
-
beforeAll(() => createTestContext())
|
|
591
|
-
afterAll(() => env.dispose())
|
|
467
|
+
import { account } from 'devflare/cloudflare'
|
|
592
468
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const counter = env.COUNTER.get(id)
|
|
596
|
-
const result = await counter.increment(2)
|
|
597
|
-
|
|
598
|
-
expect(result).toBeInstanceOf(DoubleableNumber)
|
|
599
|
-
expect(result.double).toBe(4)
|
|
600
|
-
})
|
|
469
|
+
const isLoggedIn = await account.isAuthenticated()
|
|
470
|
+
const primary = await account.getPrimaryAccount()
|
|
601
471
|
```
|
|
602
472
|
|
|
603
|
-
|
|
473
|
+
It includes helpers for:
|
|
604
474
|
|
|
605
|
-
|
|
475
|
+
- authentication and Wrangler auth inspection
|
|
476
|
+
- listing Workers, KV, D1, R2, Vectorize, and AI resources
|
|
477
|
+
- service status
|
|
478
|
+
- usage tracking
|
|
479
|
+
- limit enforcement
|
|
480
|
+
- test guardrails
|
|
481
|
+
- global and workspace account preference management
|
|
606
482
|
|
|
607
|
-
|
|
483
|
+
### Account preference storage
|
|
608
484
|
|
|
609
|
-
|
|
485
|
+
Current account preference behavior is:
|
|
610
486
|
|
|
611
|
-
|
|
487
|
+
- workspace account: stored in nearest `package.json` as `devflare.accountId`
|
|
488
|
+
- global default: cached locally in `~/.devflare/preferences.json` and synced via Devflare-managed Cloudflare KV when possible
|
|
612
489
|
|
|
613
|
-
|
|
490
|
+
This matters if your repo uses multiple Cloudflare accounts or you want deterministic test behavior across machines.
|
|
614
491
|
|
|
615
|
-
|
|
492
|
+
---
|
|
616
493
|
|
|
617
|
-
|
|
494
|
+
## Generated artifacts
|
|
618
495
|
|
|
619
|
-
|
|
496
|
+
Treat these as generated output, not source of truth:
|
|
620
497
|
|
|
621
|
-
-
|
|
622
|
-
- `
|
|
623
|
-
- `
|
|
624
|
-
- `createMockD1()`
|
|
625
|
-
- `createMockR2()`
|
|
626
|
-
- `createMockQueue()`
|
|
498
|
+
- `.devflare/`
|
|
499
|
+
- `env.d.ts`
|
|
500
|
+
- generated `wrangler.jsonc`
|
|
627
501
|
|
|
628
|
-
|
|
502
|
+
The source of truth is still:
|
|
629
503
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
-
|
|
633
|
-
- `cf.queue`
|
|
634
|
-
- `cf.scheduled`
|
|
635
|
-
- `cf.email`
|
|
636
|
-
- `cf.tail`
|
|
637
|
-
|
|
638
|
-
That consistency is a huge quality-of-life improvement.
|
|
504
|
+
- `devflare.config.ts`
|
|
505
|
+
- your source files under `src/`
|
|
506
|
+
- your test files
|
|
639
507
|
|
|
640
508
|
---
|
|
641
509
|
|
|
642
|
-
##
|
|
643
|
-
|
|
644
|
-
Browser rendering is a perfect example of Devflare adding capability above raw Miniflare.
|
|
510
|
+
## Caveats and feature maturity
|
|
645
511
|
|
|
646
|
-
|
|
512
|
+
Devflare has a broad surface area, but not every feature is equally mature.
|
|
647
513
|
|
|
648
|
-
|
|
514
|
+
### Strong core areas
|
|
649
515
|
|
|
650
|
-
|
|
516
|
+
- typed config
|
|
517
|
+
- config loading and compilation
|
|
518
|
+
- file conventions
|
|
519
|
+
- `vars`
|
|
520
|
+
- Durable Objects
|
|
521
|
+
- queues
|
|
522
|
+
- `ref()`
|
|
523
|
+
- testing ergonomics
|
|
524
|
+
- unified `env`
|
|
525
|
+
- Vite/SvelteKit integration surfaces
|
|
651
526
|
|
|
652
|
-
|
|
527
|
+
### Important caveats
|
|
653
528
|
|
|
654
|
-
|
|
529
|
+
1. **`vars` are string-only in current Devflare config**
|
|
530
|
+
- serialize more complex values yourself if needed
|
|
655
531
|
|
|
656
|
-
|
|
532
|
+
2. **`secrets` are declaration-oriented**
|
|
533
|
+
- secret values are not stored in config and are not compiled into generated Wrangler config
|
|
657
534
|
|
|
658
|
-
|
|
659
|
-
-
|
|
660
|
-
-
|
|
661
|
-
- websocket-aware local development
|
|
535
|
+
3. **`sendEmail` is lighter than core bindings**
|
|
536
|
+
- it exists in the schema and examples
|
|
537
|
+
- but it is not as fully wired as KV/D1/R2/DO/queues/services
|
|
662
538
|
|
|
663
|
-
|
|
539
|
+
4. **named service entrypoints need verification in generated output**
|
|
540
|
+
- `ref().worker('Entrypoint')` is modeled at the Devflare layer
|
|
541
|
+
- validate deployment output if named entrypoint wiring is critical to your app
|
|
664
542
|
|
|
665
|
-
|
|
543
|
+
5. **workflows, `build`, and `plugins` are lighter than the most central surfaces**
|
|
544
|
+
- they exist and matter
|
|
545
|
+
- but they are not as battle-hardened as the fetch/queue/DO/test path
|
|
666
546
|
|
|
667
|
-
|
|
668
|
-
-
|
|
669
|
-
- local development that still behaves like a coherent Worker system
|
|
547
|
+
6. **use `wrangler.passthrough` for unsupported native fields**
|
|
548
|
+
- if Wrangler supports something Devflare does not model yet, passthrough is the supported escape hatch
|
|
670
549
|
|
|
671
550
|
---
|
|
672
551
|
|
|
673
|
-
##
|
|
674
|
-
|
|
675
|
-
Devflare includes a CLI for the full development loop.
|
|
552
|
+
## Deprecations and preferred replacements
|
|
676
553
|
|
|
677
|
-
|
|
678
|
-
devflare init
|
|
679
|
-
devflare dev
|
|
680
|
-
devflare build
|
|
681
|
-
devflare deploy
|
|
682
|
-
devflare types
|
|
683
|
-
devflare doctor
|
|
684
|
-
devflare remote
|
|
685
|
-
```
|
|
554
|
+
Some older APIs still exist for compatibility.
|
|
686
555
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
556
|
+
| Older API | Prefer instead |
|
|
557
|
+
|---|---|
|
|
558
|
+
| `createMultiWorkerContext()` | `createTestContext()` |
|
|
559
|
+
| `createEntrypointScript()` | normal worker/entrypoint file patterns |
|
|
560
|
+
| `resolveRef()` | `ref()` |
|
|
561
|
+
| `serviceBinding()` | `ref().worker` or `ref().worker('Entrypoint')` |
|
|
562
|
+
| `isRemoteModeEnabled()` | `shouldSkip.*` for tests and `devflare remote ...` for mode management |
|
|
694
563
|
|
|
695
564
|
---
|
|
696
565
|
|
|
@@ -702,20 +571,19 @@ bunx --bun devflare build
|
|
|
702
571
|
├── env.d.ts
|
|
703
572
|
├── src/
|
|
704
573
|
│ ├── fetch.ts
|
|
705
|
-
│ ├── routes/
|
|
706
574
|
│ ├── queue.ts
|
|
707
575
|
│ ├── scheduled.ts
|
|
708
576
|
│ ├── email.ts
|
|
577
|
+
│ ├── routes/
|
|
709
578
|
│ ├── do.counter.ts
|
|
710
579
|
│ ├── ep.admin.ts
|
|
580
|
+
│ ├── wf.sync.ts
|
|
711
581
|
│ └── transport.ts
|
|
712
582
|
└── tests/
|
|
713
583
|
└── worker.test.ts
|
|
714
584
|
```
|
|
715
585
|
|
|
716
|
-
Not every project needs every file.
|
|
717
|
-
|
|
718
|
-
But if you follow this shape, Devflare will feel very natural.
|
|
586
|
+
Not every project needs every file, but this layout matches the Devflare mental model well.
|
|
719
587
|
|
|
720
588
|
---
|
|
721
589
|
|