devflare 1.0.0-next.1 → 1.0.0-next.11
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 +1424 -610
- package/R2.md +200 -0
- package/README.md +302 -505
- package/bin/devflare.js +8 -8
- package/dist/{account-rvrj687w.js → account-8psavtg6.js} +27 -4
- package/dist/bridge/miniflare.d.ts +6 -0
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/bridge/proxy.d.ts +5 -6
- package/dist/bridge/proxy.d.ts.map +1 -1
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/browser.d.ts +50 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/{build-mnf6v8gd.js → build-ezksv2dd.js} +26 -7
- package/dist/bundler/do-bundler.d.ts +7 -0
- package/dist/bundler/do-bundler.d.ts.map +1 -1
- package/dist/cli/commands/account.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/types.d.ts.map +1 -1
- package/dist/cli/config-path.d.ts +5 -0
- package/dist/cli/config-path.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/package-metadata.d.ts +16 -0
- package/dist/cli/package-metadata.d.ts.map +1 -0
- package/dist/config/compiler.d.ts +7 -0
- package/dist/config/compiler.d.ts.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema.d.ts +2594 -1234
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/{deploy-nhceck39.js → deploy-jdpy21t6.js} +33 -15
- package/dist/{dev-qnxet3j9.js → dev-9mq7zhww.js} +900 -234
- package/dist/dev-server/miniflare-log.d.ts +12 -0
- package/dist/dev-server/miniflare-log.d.ts.map +1 -0
- package/dist/dev-server/runtime-stdio.d.ts +8 -0
- package/dist/dev-server/runtime-stdio.d.ts.map +1 -0
- package/dist/dev-server/server.d.ts +2 -0
- package/dist/dev-server/server.d.ts.map +1 -1
- package/dist/dev-server/vite-utils.d.ts +37 -0
- package/dist/dev-server/vite-utils.d.ts.map +1 -0
- package/dist/{doctor-e8fy6fj5.js → doctor-z4ffybce.js} +73 -50
- package/dist/{durable-object-t4kbb0yt.js → durable-object-yt8v1dyn.js} +1 -1
- package/dist/index-1p814k7s.js +227 -0
- package/dist/{index-tk6ej9dj.js → index-2q3pmzrx.js} +12 -16
- package/dist/{index-67qcae0f.js → index-51s1hkw4.js} +16 -1
- package/dist/{index-ep3445yc.js → index-53xcakh8.js} +414 -171
- package/dist/{index-pf5s73n9.js → index-59df49vn.js} +11 -281
- package/dist/index-5yxg30va.js +304 -0
- package/dist/index-62b3gt2g.js +12 -0
- package/dist/index-6h8xbs75.js +44 -0
- package/dist/index-8gtqgb3q.js +529 -0
- package/dist/{index-gz1gndna.js → index-9wt9x09k.js} +42 -62
- package/dist/index-dr6sbp8d.js +39 -0
- package/dist/index-fef08w43.js +231 -0
- package/dist/index-k7r18na8.js +0 -0
- package/dist/{index-m2q41jwa.js → index-n932ytmq.js} +9 -1
- package/dist/{index-07q6yxyc.js → index-v8vvsn9x.js} +1 -0
- package/dist/index-vky23txa.js +70 -0
- package/dist/{index-z14anrqp.js → index-wfbfz02q.js} +14 -15
- package/dist/index-ws68xvq2.js +311 -0
- package/dist/{index-hcex3rgh.js → index-wyf3s77s.js} +85 -8
- package/dist/index-xqfbd9fx.js +195 -0
- package/dist/index-xxwbb2nt.js +322 -0
- package/dist/index-y1d8za14.js +196 -0
- package/dist/{init-f9mgmew3.js → init-na2atvz2.js} +42 -55
- package/dist/router/types.d.ts +24 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +249 -8
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/exports.d.ts +50 -55
- package/dist/runtime/exports.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +8 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/middleware.d.ts +77 -60
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/router.d.ts +7 -0
- package/dist/runtime/router.d.ts.map +1 -0
- package/dist/runtime/validation.d.ts +1 -1
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/src/browser.js +150 -0
- package/dist/src/cli/index.js +10 -0
- package/dist/{cloudflare → src/cloudflare}/index.js +3 -3
- package/dist/{decorators → src/decorators}/index.js +2 -2
- package/dist/src/index.js +132 -0
- package/dist/src/runtime/index.js +111 -0
- package/dist/{sveltekit → src/sveltekit}/index.js +14 -6
- package/dist/{test → src/test}/index.js +22 -13
- package/dist/{vite → src/vite}/index.js +128 -59
- package/dist/sveltekit/platform.d.ts.map +1 -1
- package/dist/test/bridge-context.d.ts +5 -2
- package/dist/test/bridge-context.d.ts.map +1 -1
- package/dist/test/cf.d.ts +25 -11
- package/dist/test/cf.d.ts.map +1 -1
- package/dist/test/email.d.ts +16 -7
- package/dist/test/email.d.ts.map +1 -1
- package/dist/test/queue.d.ts.map +1 -1
- package/dist/test/resolve-service-bindings.d.ts.map +1 -1
- package/dist/test/scheduled.d.ts.map +1 -1
- package/dist/test/simple-context.d.ts +1 -1
- package/dist/test/simple-context.d.ts.map +1 -1
- package/dist/test/tail.d.ts +2 -1
- package/dist/test/tail.d.ts.map +1 -1
- package/dist/test/worker.d.ts +6 -0
- package/dist/test/worker.d.ts.map +1 -1
- package/dist/transform/durable-object.d.ts.map +1 -1
- package/dist/transform/worker-entrypoint.d.ts.map +1 -1
- package/dist/{types-5nyrz1sz.js → types-nq5acrwh.js} +30 -16
- package/dist/utils/entrypoint-discovery.d.ts +6 -3
- package/dist/utils/entrypoint-discovery.d.ts.map +1 -1
- package/dist/utils/send-email.d.ts +15 -0
- package/dist/utils/send-email.d.ts.map +1 -0
- package/dist/vite/plugin.d.ts.map +1 -1
- package/dist/worker-entry/composed-worker.d.ts +13 -0
- package/dist/worker-entry/composed-worker.d.ts.map +1 -0
- package/dist/worker-entry/routes.d.ts +22 -0
- package/dist/worker-entry/routes.d.ts.map +1 -0
- package/dist/{worker-entrypoint-m9th0rg0.js → worker-entrypoint-c259fmfs.js} +1 -1
- package/package.json +21 -19
- package/dist/index.js +0 -298
- package/dist/runtime/index.js +0 -111
package/README.md
CHANGED
|
@@ -2,101 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
**Build Cloudflare Workers like an application, not a pile of glue.**
|
|
4
4
|
|
|
5
|
-
Devflare is a developer-first
|
|
5
|
+
Devflare is a developer-first layer on top of Cloudflare Workers, Miniflare, Bun, Vite, and Wrangler-compatible config.
|
|
6
6
|
|
|
7
7
|
It gives you:
|
|
8
8
|
|
|
9
|
-
- a clean file structure
|
|
10
9
|
- typed config with `defineConfig()`
|
|
11
|
-
-
|
|
12
|
-
-
|
|
10
|
+
- convention-friendly worker surfaces
|
|
11
|
+
- local orchestration for multi-surface Worker apps
|
|
13
12
|
- first-class Durable Object and multi-worker workflows
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- test helpers that mirror real handler surfaces
|
|
14
|
+
- worker-safe runtime helpers under `devflare/runtime`
|
|
15
|
+
- optional Vite and SvelteKit integration when your package actually uses them
|
|
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**.
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## What makes it different from plain Miniflare?
|
|
64
|
-
|
|
65
|
-
Devflare does not replace Miniflare.
|
|
66
|
-
|
|
67
|
-
It **extends** it.
|
|
68
|
-
|
|
69
|
-
Miniflare gives you local execution.
|
|
70
|
-
Devflare adds the developer workflow around it:
|
|
71
|
-
|
|
72
|
-
- convention-first file discovery
|
|
73
|
-
- config compilation to Wrangler-compatible output
|
|
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
|
|
78
|
-
- framework-aware integration for Vite and SvelteKit
|
|
79
|
-
|
|
80
|
-
In short:
|
|
81
|
-
|
|
82
|
-
> Devflare helps you build Cloudflare systems with clear boundaries and smooth local development.
|
|
21
|
+
For the deeper public contract, caveats, and current feature boundaries, see [`LLM.md`](./LLM.md).
|
|
83
22
|
|
|
84
23
|
---
|
|
85
24
|
|
|
86
25
|
## Install
|
|
87
26
|
|
|
88
|
-
For a
|
|
27
|
+
For a worker-only project, Devflare works fine with just the Worker toolchain:
|
|
89
28
|
|
|
90
29
|
```bash
|
|
91
|
-
bun add -d devflare wrangler
|
|
30
|
+
bun add -d devflare wrangler @cloudflare/workers-types
|
|
92
31
|
```
|
|
93
32
|
|
|
94
|
-
If
|
|
33
|
+
If the current package also uses Vite, add Vite and the Cloudflare Vite plugin too:
|
|
95
34
|
|
|
96
35
|
```bash
|
|
97
|
-
bun add -d vite @cloudflare/vite-plugin
|
|
36
|
+
bun add -d devflare wrangler @cloudflare/workers-types vite @cloudflare/vite-plugin
|
|
98
37
|
```
|
|
99
38
|
|
|
39
|
+
A local `vite.config.*` opts that package into Vite-backed flows. Without one, Devflare stays in worker-only mode.
|
|
40
|
+
|
|
100
41
|
---
|
|
101
42
|
|
|
102
43
|
## Quick start
|
|
@@ -108,7 +49,11 @@ bun add -d vite @cloudflare/vite-plugin
|
|
|
108
49
|
import { defineConfig } from 'devflare'
|
|
109
50
|
|
|
110
51
|
export default defineConfig({
|
|
111
|
-
name: 'hello-worker'
|
|
52
|
+
name: 'hello-worker',
|
|
53
|
+
compatibilityDate: '2026-03-17',
|
|
54
|
+
files: {
|
|
55
|
+
fetch: 'src/fetch.ts'
|
|
56
|
+
}
|
|
112
57
|
})
|
|
113
58
|
```
|
|
114
59
|
|
|
@@ -116,8 +61,15 @@ export default defineConfig({
|
|
|
116
61
|
|
|
117
62
|
```ts
|
|
118
63
|
// src/fetch.ts
|
|
119
|
-
|
|
120
|
-
|
|
64
|
+
import type { FetchEvent } from 'devflare/runtime'
|
|
65
|
+
|
|
66
|
+
export async function fetch({ request }: FetchEvent): Promise<Response> {
|
|
67
|
+
const url = new URL(request.url)
|
|
68
|
+
return new Response(
|
|
69
|
+
url.pathname === '/'
|
|
70
|
+
? 'Hello from Devflare'
|
|
71
|
+
: `Hello from Devflare: ${url.pathname}`
|
|
72
|
+
)
|
|
121
73
|
}
|
|
122
74
|
```
|
|
123
75
|
|
|
@@ -127,7 +79,7 @@ export default async function fetch(): Promise<Response> {
|
|
|
127
79
|
bunx --bun devflare types
|
|
128
80
|
```
|
|
129
81
|
|
|
130
|
-
This
|
|
82
|
+
This generates `env.d.ts` so bindings, secrets, and discovered entrypoints stay typed.
|
|
131
83
|
|
|
132
84
|
### 4. Start development
|
|
133
85
|
|
|
@@ -157,477 +109,348 @@ describe('hello-worker', () => {
|
|
|
157
109
|
|
|
158
110
|
---
|
|
159
111
|
|
|
160
|
-
##
|
|
161
|
-
|
|
162
|
-
Devflare works best when each concern lives in its own file.
|
|
112
|
+
## Package entrypoints
|
|
163
113
|
|
|
164
|
-
|
|
114
|
+
Use subpaths intentionally.
|
|
165
115
|
|
|
166
|
-
|
|
|
116
|
+
| Import | Use for |
|
|
167
117
|
|---|---|
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `wf.*.ts` | workflows |
|
|
176
|
-
| `src/transport.ts` | custom serialization across boundaries |
|
|
177
|
-
|
|
178
|
-
This is more than style.
|
|
118
|
+
| `devflare` | main package entrypoint: config helpers, `ref()`, unified `env`, bridge helpers, CLI helpers, decorators |
|
|
119
|
+
| `devflare/runtime` | worker-safe runtime helpers like `env`, `ctx`, `event`, `locals`, event types/getters, middleware helpers |
|
|
120
|
+
| `devflare/test` | `createTestContext`, `cf.*`, mock helpers, bridge test context, skip helpers |
|
|
121
|
+
| `devflare/vite` | Vite integration |
|
|
122
|
+
| `devflare/sveltekit` | SvelteKit integration |
|
|
123
|
+
| `devflare/cloudflare` | Cloudflare account/auth/usage/limits/preferences helpers |
|
|
124
|
+
| `devflare/decorators` | decorators only |
|
|
179
125
|
|
|
180
|
-
|
|
126
|
+
### Runtime import rule of thumb
|
|
181
127
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
## Example config
|
|
128
|
+
- use `import { env } from 'devflare/runtime'` for **strict request-scoped runtime access**
|
|
129
|
+
- use `import { env } from 'devflare'` when you want the **unified proxy** that can fall back to test or bridge context outside a live request
|
|
185
130
|
|
|
186
|
-
|
|
187
|
-
import { defineConfig } from 'devflare'
|
|
188
|
-
|
|
189
|
-
export default defineConfig({
|
|
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
|
-
```
|
|
131
|
+
The reduced worker-safe main entry for `devflare` is selected through the package `browser` export condition. `devflare/runtime` is the direct worker-safe runtime subpath.
|
|
221
132
|
|
|
222
133
|
---
|
|
223
134
|
|
|
224
|
-
##
|
|
135
|
+
## Event-first handlers
|
|
225
136
|
|
|
226
|
-
|
|
137
|
+
Fresh Devflare code should be event-first:
|
|
227
138
|
|
|
228
|
-
|
|
139
|
+
- `fetch(event: FetchEvent)`
|
|
140
|
+
- `queue(event: QueueEvent)`
|
|
141
|
+
- `scheduled(event: ScheduledEvent)`
|
|
142
|
+
- `email(event: EmailEvent)`
|
|
143
|
+
- Durable Object lifecycle handlers with their matching event types
|
|
229
144
|
|
|
230
|
-
|
|
231
|
-
import { defineConfig } from 'devflare'
|
|
145
|
+
Devflare stores the active event in `AsyncLocalStorage`, and Devflare-managed entrypoints establish that context for you before your handler runs. That includes generated worker wrappers, the local dev server, and `createTestContext()` helper surfaces.
|
|
232
146
|
|
|
233
|
-
|
|
234
|
-
name: 'my-api',
|
|
235
|
-
files: {
|
|
236
|
-
routes: {
|
|
237
|
-
dir: 'src/routes',
|
|
238
|
-
prefix: '/api'
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
})
|
|
242
|
-
```
|
|
147
|
+
Helpers deeper in the same call trail can recover the current surface with getters like:
|
|
243
148
|
|
|
244
|
-
|
|
149
|
+
- `getFetchEvent()`
|
|
150
|
+
- `getQueueEvent()`
|
|
151
|
+
- `getScheduledEvent()`
|
|
152
|
+
- `getEmailEvent()`
|
|
153
|
+
- `getDurableObjectFetchEvent()`
|
|
245
154
|
|
|
246
|
-
|
|
155
|
+
Every getter also exposes `.safe()`, which returns `null` instead of throwing.
|
|
247
156
|
|
|
248
|
-
|
|
157
|
+
In normal app code you should not need to call `runWithEventContext()` or `runWithContext()` yourself.
|
|
249
158
|
|
|
250
|
-
|
|
159
|
+
---
|
|
251
160
|
|
|
252
|
-
|
|
253
|
-
- consume them in `src/queue.ts`
|
|
254
|
-
- test them with `cf.queue`
|
|
161
|
+
## HTTP structure that matches the runtime today
|
|
255
162
|
|
|
256
|
-
|
|
257
|
-
// devflare.config.ts
|
|
258
|
-
import { defineConfig } from 'devflare'
|
|
163
|
+
The built-in HTTP story now has **two cooperating layers**:
|
|
259
164
|
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
```
|
|
165
|
+
1. an optional global fetch module at `src/fetch.ts`
|
|
166
|
+
2. a built-in file router rooted at `src/routes/**`
|
|
281
167
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
import { env } from 'devflare'
|
|
168
|
+
Use `src/fetch.ts` for request-wide middleware and whole-app HTTP concerns.
|
|
169
|
+
Use `src/routes/**` for leaf handlers.
|
|
285
170
|
|
|
286
|
-
|
|
287
|
-
const url = new URL(request.url)
|
|
171
|
+
### Request-wide middleware
|
|
288
172
|
|
|
289
|
-
|
|
290
|
-
const task = {
|
|
291
|
-
id: crypto.randomUUID(),
|
|
292
|
-
type: 'resize-image'
|
|
293
|
-
}
|
|
173
|
+
Use `sequence(...)` with a single exported primary fetch entry:
|
|
294
174
|
|
|
295
|
-
|
|
296
|
-
|
|
175
|
+
```ts
|
|
176
|
+
import { sequence } from 'devflare/runtime'
|
|
177
|
+
import type { FetchEvent, ResolveFetch } from 'devflare/runtime'
|
|
178
|
+
|
|
179
|
+
async function corsHandle(event: FetchEvent, resolve: ResolveFetch): Promise<Response> {
|
|
180
|
+
if (event.request.method === 'OPTIONS') {
|
|
181
|
+
return new Response(null, {
|
|
182
|
+
headers: {
|
|
183
|
+
'Access-Control-Allow-Origin': '*',
|
|
184
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
185
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
|
186
|
+
}
|
|
187
|
+
})
|
|
297
188
|
}
|
|
298
189
|
|
|
299
|
-
|
|
190
|
+
const response = await resolve(event)
|
|
191
|
+
const next = new Response(response.body, response)
|
|
192
|
+
next.headers.set('Access-Control-Allow-Origin', '*')
|
|
193
|
+
return next
|
|
300
194
|
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
```ts
|
|
304
|
-
// src/queue.ts
|
|
305
|
-
import type { MessageBatch } from '@cloudflare/workers-types'
|
|
306
195
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
type: string
|
|
196
|
+
async function appFetch({ request }: FetchEvent): Promise<Response> {
|
|
197
|
+
return Response.json({ path: new URL(request.url).pathname })
|
|
310
198
|
}
|
|
311
199
|
|
|
312
|
-
export
|
|
313
|
-
batch: MessageBatch<Task>,
|
|
314
|
-
env: DevflareEnv
|
|
315
|
-
): Promise<void> {
|
|
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
|
-
}
|
|
200
|
+
export const handle = sequence(corsHandle, appFetch)
|
|
328
201
|
```
|
|
329
202
|
|
|
330
|
-
|
|
203
|
+
Important rules:
|
|
331
204
|
|
|
332
|
-
|
|
205
|
+
- `fetch` and `handle` are two names for the same primary HTTP entry
|
|
206
|
+
- export **one** of them from a given module, never both
|
|
207
|
+
- if `src/fetch.ts` exports same-module `GET()` / `POST()` handlers, those run before file routes for matching methods
|
|
208
|
+
- for route-tree apps, keep `src/fetch.ts` focused on request-wide middleware and put leaf handlers in `src/routes/**`
|
|
333
209
|
|
|
334
|
-
|
|
210
|
+
### File router
|
|
335
211
|
|
|
336
|
-
|
|
212
|
+
`src/routes/**` is now a real built-in router.
|
|
337
213
|
|
|
338
|
-
|
|
214
|
+
By default, Devflare discovers `src/routes/**` automatically when that directory exists.
|
|
339
215
|
|
|
340
|
-
|
|
341
|
-
// src/email.ts
|
|
342
|
-
import { env } from 'devflare'
|
|
343
|
-
import type { ForwardableEmailMessage } from '@cloudflare/workers-types'
|
|
344
|
-
|
|
345
|
-
export async function email(message: ForwardableEmailMessage): Promise<void> {
|
|
346
|
-
const id = crypto.randomUUID()
|
|
347
|
-
|
|
348
|
-
// Log the email to KV for later inspection
|
|
349
|
-
await env.EMAIL_LOG.put(
|
|
350
|
-
`email:${id}`,
|
|
351
|
-
JSON.stringify({
|
|
352
|
-
from: message.from,
|
|
353
|
-
to: message.to,
|
|
354
|
-
receivedAt: new Date().toISOString()
|
|
355
|
-
})
|
|
356
|
-
)
|
|
216
|
+
You can customize or disable it with `files.routes`:
|
|
357
217
|
|
|
358
|
-
|
|
359
|
-
|
|
218
|
+
```ts
|
|
219
|
+
export default {
|
|
220
|
+
name: 'api-worker',
|
|
221
|
+
files: {
|
|
222
|
+
fetch: 'src/fetch.ts',
|
|
223
|
+
routes: {
|
|
224
|
+
dir: 'src/routes',
|
|
225
|
+
prefix: '/api'
|
|
226
|
+
}
|
|
227
|
+
}
|
|
360
228
|
}
|
|
361
229
|
```
|
|
362
230
|
|
|
363
|
-
|
|
231
|
+
- `files.routes.dir` changes the route root
|
|
232
|
+
- `files.routes.prefix` mounts the route tree under a fixed prefix
|
|
233
|
+
- `files.routes: false` disables automatic route discovery entirely
|
|
364
234
|
|
|
365
|
-
|
|
235
|
+
Route conventions:
|
|
366
236
|
|
|
367
|
-
|
|
237
|
+
- `src/routes/index.ts` → `/`
|
|
238
|
+
- `src/routes/users/index.ts` → `/users`
|
|
239
|
+
- `src/routes/users/[id].ts` → `/users/:id`
|
|
240
|
+
- `src/routes/blog/[...slug].ts` → `/blog/*` with `params.slug = 'a/b'`
|
|
241
|
+
- `src/routes/docs/[[...slug]].ts` → optional catch-all, including `/docs`
|
|
242
|
+
- files or directories that start with `_` are ignored so you can keep route-local helpers nearby
|
|
368
243
|
|
|
369
|
-
|
|
244
|
+
Route module exports use the same fetch-module rules as `src/fetch.ts`:
|
|
370
245
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
246
|
+
- `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `ALL`
|
|
247
|
+
- route-local `fetch(event)` / `handle(event, resolve)` if you want per-route middleware
|
|
248
|
+
- `HEAD` falls back to `GET` when no explicit `HEAD` export exists
|
|
374
249
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// Locked to a specific recipient — every send goes to admin
|
|
383
|
-
ADMIN_EMAIL: { destinationAddress: 'admin@example.com' },
|
|
384
|
-
|
|
385
|
-
// Open — you choose the recipient per message
|
|
386
|
-
EMAIL: {}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
})
|
|
250
|
+
Route params are available on `event.params`.
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
// src/routes/users/[id].ts
|
|
254
|
+
export async function GET(event): Promise<Response> {
|
|
255
|
+
return Response.json({ id: event.params.id })
|
|
256
|
+
}
|
|
390
257
|
```
|
|
391
258
|
|
|
392
|
-
|
|
259
|
+
### Resolution order
|
|
393
260
|
|
|
394
|
-
|
|
395
|
-
- `env.EMAIL` — you provide the destination when composing the message
|
|
261
|
+
When a request comes in, Devflare resolves HTTP in this order:
|
|
396
262
|
|
|
397
|
-
|
|
263
|
+
1. create the initial fetch event and populate `event.params` from the matched file route, if any
|
|
264
|
+
2. run the primary `src/fetch.ts` `fetch` / `handle` export if present
|
|
265
|
+
3. inside `resolve(event)`, try same-module method handlers from `src/fetch.ts`
|
|
266
|
+
4. if no same-module handler matched, dispatch to the matched route module from `src/routes/**`
|
|
267
|
+
5. return `404 Not Found` if nothing matched
|
|
398
268
|
|
|
399
|
-
|
|
269
|
+
That means global middleware can see `event.params`, while route files remain the main leaf-handler story.
|
|
270
|
+
|
|
271
|
+
For example:
|
|
400
272
|
|
|
401
273
|
```ts
|
|
402
|
-
|
|
274
|
+
// src/fetch.ts
|
|
275
|
+
import { sequence } from 'devflare/runtime'
|
|
403
276
|
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
277
|
+
export const handle = sequence(async (event, resolve) => {
|
|
278
|
+
const response = await resolve(event)
|
|
279
|
+
const next = new Response(response.body, response)
|
|
280
|
+
next.headers.set('x-user-id', event.params.id ?? 'none')
|
|
281
|
+
return next
|
|
409
282
|
})
|
|
410
|
-
|
|
411
|
-
expect(response.ok).toBe(true)
|
|
412
283
|
```
|
|
413
284
|
|
|
414
285
|
---
|
|
415
286
|
|
|
416
|
-
##
|
|
287
|
+
## Config highlights
|
|
417
288
|
|
|
418
|
-
Devflare
|
|
289
|
+
Devflare looks for these config filenames:
|
|
419
290
|
|
|
420
|
-
|
|
291
|
+
- `devflare.config.ts`
|
|
292
|
+
- `devflare.config.mts`
|
|
293
|
+
- `devflare.config.js`
|
|
294
|
+
- `devflare.config.mjs`
|
|
421
295
|
|
|
422
|
-
|
|
423
|
-
bindings: {
|
|
424
|
-
durableObjects: {
|
|
425
|
-
COUNTER: 'Counter'
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
Place the class in a `do.*.ts` file and Devflare handles the discovery flow.
|
|
296
|
+
The most important top-level keys are:
|
|
431
297
|
|
|
432
|
-
|
|
298
|
+
- `name`
|
|
299
|
+
- `accountId`
|
|
300
|
+
- `compatibilityDate`
|
|
301
|
+
- `compatibilityFlags`
|
|
302
|
+
- `files`
|
|
303
|
+
- `bindings`
|
|
304
|
+
- `triggers`
|
|
305
|
+
- `vars`
|
|
306
|
+
- `secrets`
|
|
307
|
+
- `routes`
|
|
308
|
+
- `wsRoutes`
|
|
309
|
+
- `assets`
|
|
310
|
+
- `limits`
|
|
311
|
+
- `observability`
|
|
312
|
+
- `migrations`
|
|
313
|
+
- `rolldown`
|
|
314
|
+
- `vite`
|
|
315
|
+
- `env`
|
|
316
|
+
- `wrangler.passthrough`
|
|
433
317
|
|
|
434
|
-
|
|
318
|
+
### `vars` vs `secrets`
|
|
435
319
|
|
|
436
|
-
|
|
437
|
-
// devflare.config.ts
|
|
438
|
-
import { defineConfig } from 'devflare'
|
|
439
|
-
|
|
440
|
-
export default defineConfig({
|
|
441
|
-
name: 'sessions-app',
|
|
442
|
-
bindings: {
|
|
443
|
-
durableObjects: {
|
|
444
|
-
SESSION: 'SessionStore'
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
})
|
|
448
|
-
```
|
|
320
|
+
Keep these separate:
|
|
449
321
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
import { DurableObject } from 'cloudflare:workers'
|
|
322
|
+
- `vars` are **string-valued config bindings** that compile into generated Wrangler config
|
|
323
|
+
- `secrets` are **declarations of expected runtime secret bindings**
|
|
453
324
|
|
|
454
|
-
|
|
455
|
-
private data = new Map<string, string>()
|
|
325
|
+
`loadConfig()` does **not** do dotenv loading by itself.
|
|
456
326
|
|
|
457
|
-
|
|
458
|
-
return this.data.get(key) ?? null
|
|
459
|
-
}
|
|
327
|
+
When you run the CLI under Bun, Bun may already have loaded `.env` values into `process.env` before your config executes.
|
|
460
328
|
|
|
461
|
-
|
|
462
|
-
this.data.set(key, value)
|
|
463
|
-
}
|
|
329
|
+
Important boundary:
|
|
464
330
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
```
|
|
331
|
+
- `.env*` files are config/build-time inputs only insofar as the surrounding host tool has already populated `process.env`
|
|
332
|
+
- Devflare does **not** currently provide a first-class `.dev.vars*` loader for worker-only dev mode or `createTestContext()`
|
|
333
|
+
- example files like `.env.example` and `.dev.vars.example` are a team convention, not a Devflare feature
|
|
470
334
|
|
|
471
|
-
|
|
472
|
-
// src/fetch.ts
|
|
473
|
-
import { env } from 'devflare'
|
|
335
|
+
Practical convention:
|
|
474
336
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const sessionId = url.searchParams.get('session') ?? 'default'
|
|
337
|
+
- use `.env.example` for config-time/build-time variables read from `process.env`
|
|
338
|
+
- use `.dev.vars.example` only if your project intentionally relies on upstream `.dev.vars` local-runtime workflows
|
|
478
339
|
|
|
479
|
-
|
|
480
|
-
const session = env.SESSION.get(id)
|
|
340
|
+
### `config.env`
|
|
481
341
|
|
|
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
|
-
}
|
|
342
|
+
`config.env` is a Devflare merge layer, not a raw Wrangler env mirror.
|
|
488
343
|
|
|
489
|
-
|
|
490
|
-
const key = url.searchParams.get('key') ?? 'name'
|
|
491
|
-
const value = await session.getValue(key)
|
|
492
|
-
return Response.json({ value })
|
|
493
|
-
}
|
|
344
|
+
When you select `--env name`, Devflare merges `config.env[name]` into the base config before compiling.
|
|
494
345
|
|
|
495
|
-
|
|
496
|
-
await session.clearAll()
|
|
497
|
-
return Response.json({ ok: true })
|
|
498
|
-
}
|
|
346
|
+
### Native config vs Wrangler coverage
|
|
499
347
|
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
```
|
|
348
|
+
Devflare natively models the Worker config it actively composes around:
|
|
503
349
|
|
|
504
|
-
|
|
350
|
+
- handler/file surfaces
|
|
351
|
+
- core bindings and service composition
|
|
352
|
+
- routes, assets, migrations, observability, and limits
|
|
353
|
+
- Vite and Rolldown metadata
|
|
354
|
+
- environment overlays
|
|
505
355
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
3. get a stub with `env.SESSION.get(env.SESSION.idFromName(...))`
|
|
509
|
-
4. call your methods from `fetch`
|
|
356
|
+
It does **not** try to re-model every Wrangler key as a first-class Devflare schema field.
|
|
357
|
+
For unsupported Wrangler options, use `wrangler.passthrough`.
|
|
510
358
|
|
|
511
|
-
|
|
359
|
+
Current compile order is:
|
|
512
360
|
|
|
513
|
-
|
|
361
|
+
1. Devflare compiles native config into Wrangler-compatible output
|
|
362
|
+
2. `wrangler.passthrough` is shallow-merged on top
|
|
363
|
+
3. if the same key exists in both places, the passthrough value wins
|
|
514
364
|
|
|
515
365
|
```ts
|
|
516
|
-
import { defineConfig, ref } from 'devflare'
|
|
517
|
-
|
|
518
|
-
const authWorker = ref(() => import('../auth/devflare.config'))
|
|
519
|
-
|
|
520
366
|
export default defineConfig({
|
|
521
|
-
name: '
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
367
|
+
name: 'advanced-worker',
|
|
368
|
+
files: {
|
|
369
|
+
fetch: 'src/fetch.ts'
|
|
370
|
+
},
|
|
371
|
+
wrangler: {
|
|
372
|
+
passthrough: {
|
|
373
|
+
placement: {
|
|
374
|
+
mode: 'smart'
|
|
375
|
+
}
|
|
525
376
|
}
|
|
526
377
|
}
|
|
527
378
|
})
|
|
528
379
|
```
|
|
529
380
|
|
|
530
|
-
|
|
381
|
+
Special case: `wrangler.passthrough.main` tells higher-level `build`, `deploy`, and `devflare/vite` flows to stop generating a composed `.devflare/worker-entrypoints/main.ts` and use your explicit main entry instead.
|
|
531
382
|
|
|
532
383
|
---
|
|
533
384
|
|
|
534
|
-
##
|
|
385
|
+
## Bindings
|
|
535
386
|
|
|
536
|
-
|
|
387
|
+
Devflare natively models:
|
|
537
388
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
389
|
+
- KV
|
|
390
|
+
- D1
|
|
391
|
+
- R2
|
|
392
|
+
- Durable Objects
|
|
393
|
+
- Queues
|
|
394
|
+
- Services
|
|
395
|
+
- AI
|
|
396
|
+
- Vectorize
|
|
397
|
+
- Hyperdrive
|
|
398
|
+
- Browser Rendering
|
|
399
|
+
- Analytics Engine
|
|
400
|
+
- `sendEmail`
|
|
546
401
|
|
|
547
|
-
|
|
548
|
-
this.value = n
|
|
549
|
-
}
|
|
402
|
+
### Caveats worth knowing up front
|
|
550
403
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
404
|
+
- KV, D1, R2, Durable Objects, queues, and the core test/runtime flow are the strongest surfaces
|
|
405
|
+
- AI and Vectorize are remote-oriented bindings
|
|
406
|
+
- named service entrypoints are modeled at the Devflare layer, but validate generated deployment output if they are critical to your app
|
|
407
|
+
- `sendEmail` is modeled through config compilation, generated env types, and local runtime/test flows
|
|
408
|
+
- R2 bindings are real in local dev/test/runtime flows, but Devflare does **not** publish a stable browser-facing local bucket URL contract; browser-visible local asset flows should go through your Worker routes
|
|
556
409
|
|
|
557
|
-
|
|
558
|
-
// src/transport.ts
|
|
559
|
-
import { DoubleableNumber } from './DoubleableNumber'
|
|
410
|
+
For R2 delivery strategy guidance, see [`R2.md`](./R2.md).
|
|
560
411
|
|
|
561
|
-
|
|
562
|
-
DoubleableNumber: {
|
|
563
|
-
encode: (v: unknown) => v instanceof DoubleableNumber && v.value,
|
|
564
|
-
decode: (v: number) => new DoubleableNumber(v)
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
```ts
|
|
570
|
-
// src/do.counter.ts
|
|
571
|
-
import { DoubleableNumber } from './DoubleableNumber'
|
|
572
|
-
|
|
573
|
-
export class Counter {
|
|
574
|
-
private count = 0
|
|
575
|
-
|
|
576
|
-
increment(n: number = 1): DoubleableNumber {
|
|
577
|
-
this.count += n
|
|
578
|
-
return new DoubleableNumber(this.count)
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
```
|
|
412
|
+
---
|
|
582
413
|
|
|
583
|
-
|
|
584
|
-
// tests/counter.test.ts
|
|
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'
|
|
414
|
+
## Dev, build, deploy, Vite, and Rolldown
|
|
589
415
|
|
|
590
|
-
|
|
591
|
-
afterAll(() => env.dispose())
|
|
416
|
+
Devflare is worker-only first.
|
|
592
417
|
|
|
593
|
-
|
|
594
|
-
const id = env.COUNTER.idFromName('main')
|
|
595
|
-
const counter = env.COUNTER.get(id)
|
|
596
|
-
const result = await counter.increment(2)
|
|
418
|
+
### Mode selection
|
|
597
419
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
```
|
|
420
|
+
- a local `vite.config.*` opts the current package into **Vite-backed** flows
|
|
421
|
+
- Vite-related dependencies without a local config do **not** switch the package into Vite mode
|
|
422
|
+
- without a local `vite.config.*`, `dev`, `build`, and `deploy` stay in **worker-only** mode
|
|
602
423
|
|
|
603
|
-
|
|
424
|
+
### Mental model
|
|
604
425
|
|
|
605
|
-
|
|
426
|
+
Vite and Rolldown both matter here, but they do different jobs:
|
|
606
427
|
|
|
607
|
-
|
|
428
|
+
- **Vite** is the optional outer app/framework host. Devflare only enters Vite-backed mode when the current package has a local `vite.config.*`, and then Devflare plugs Worker-aware config, auxiliary DO workers, and Cloudflare/Vite interop into that pipeline.
|
|
429
|
+
- **Rolldown** is the inner builder Devflare uses when Devflare itself needs to transform Worker code into runnable bundles. Today that matters most in the Durable Object path and its watch/rebuild loop.
|
|
608
430
|
|
|
609
|
-
|
|
431
|
+
Short version:
|
|
610
432
|
|
|
611
|
-
|
|
433
|
+
- no local `vite.config.*` → no Vite process; Devflare stays worker-only
|
|
434
|
+
- `.svelte` imported by a Durable Object → that compilation belongs to the Rolldown plugin pipeline, not to the main Vite app build
|
|
435
|
+
- generated `.devflare/worker-entrypoints/main.ts` is separate Devflare glue that composes worker surfaces when needed
|
|
612
436
|
|
|
613
|
-
###
|
|
437
|
+
### Current behavior that matters
|
|
614
438
|
|
|
615
|
-
|
|
439
|
+
- worker-only `dev` is a real first-class path
|
|
440
|
+
- `build` and `deploy` skip Vite when the current package has no local `vite.config.*`
|
|
441
|
+
- higher-level `build`, `deploy`, and `devflare/vite` flows currently synthesize `.devflare/worker-entrypoints/main.ts` whenever a fetch, route tree, queue, scheduled, or email surface is discovered
|
|
442
|
+
- `wrangler.passthrough.main` disables that composed-entry generation path
|
|
443
|
+
- Rolldown currently matters most in the Durable Object bundler path, including unified dev flows where Vite hosts the app but Rolldown still rebuilds DO worker code
|
|
616
444
|
|
|
617
|
-
|
|
445
|
+
For the full contract-level explanation and a concrete Rolldown + Svelte example, see [`LLM.md`](./LLM.md).
|
|
618
446
|
|
|
619
|
-
|
|
447
|
+
---
|
|
620
448
|
|
|
621
|
-
|
|
622
|
-
- `withTestContext()`
|
|
623
|
-
- `createMockKV()`
|
|
624
|
-
- `createMockD1()`
|
|
625
|
-
- `createMockR2()`
|
|
626
|
-
- `createMockQueue()`
|
|
449
|
+
## Testing
|
|
627
450
|
|
|
628
|
-
|
|
451
|
+
Use `devflare/test`.
|
|
629
452
|
|
|
630
|
-
|
|
453
|
+
The high-level entrypoint is `createTestContext()`, and the unified helper surface is `cf`:
|
|
631
454
|
|
|
632
455
|
- `cf.worker`
|
|
633
456
|
- `cf.queue`
|
|
@@ -635,56 +458,52 @@ You can test each surface directly:
|
|
|
635
458
|
- `cf.email`
|
|
636
459
|
- `cf.tail`
|
|
637
460
|
|
|
638
|
-
|
|
461
|
+
### `createTestContext()` autodiscovery
|
|
639
462
|
|
|
640
|
-
|
|
463
|
+
If you omit the config path, `createTestContext()` walks upward from the calling test file and looks for the nearest supported config filename:
|
|
641
464
|
|
|
642
|
-
|
|
465
|
+
- `devflare.config.ts`
|
|
466
|
+
- `devflare.config.mts`
|
|
467
|
+
- `devflare.config.js`
|
|
468
|
+
- `devflare.config.mjs`
|
|
643
469
|
|
|
644
|
-
|
|
470
|
+
It also auto-detects conventional `src/fetch.ts`, `src/queue.ts`, `src/scheduled.ts`, `src/email.ts`, built-in `src/routes/**`, and `src/tail.ts` when they are present.
|
|
645
471
|
|
|
646
|
-
|
|
472
|
+
### Important current testing truth
|
|
647
473
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
### `devflare/vite`
|
|
655
|
-
|
|
656
|
-
Use this when you want:
|
|
657
|
-
|
|
658
|
-
- config compilation during dev and build
|
|
659
|
-
- worker-aware transforms
|
|
660
|
-
- auxiliary Durable Object worker generation
|
|
661
|
-
- websocket-aware local development
|
|
662
|
-
|
|
663
|
-
### `devflare/sveltekit`
|
|
664
|
-
|
|
665
|
-
Use this when your project needs:
|
|
666
|
-
|
|
667
|
-
- a Devflare-aware platform layer
|
|
668
|
-
- smooth SvelteKit + Worker binding integration
|
|
669
|
-
- local development that still behaves like a coherent Worker system
|
|
474
|
+
- `cf.worker.fetch()` returns when the handler resolves and does **not** eagerly wait for all `waitUntil()` work
|
|
475
|
+
- `cf.worker.fetch()` and the shorthand helpers dispatch through both `src/fetch.ts` and built-in `src/routes/**` file routes when present
|
|
476
|
+
- `cf.queue.trigger()` and `cf.scheduled.trigger()` do wait for queued background work before they return
|
|
477
|
+
- `cf.tail.trigger()` is exported, and `createTestContext()` auto-detects `src/tail.ts` when present; there is still no public `files.tail` config key
|
|
478
|
+
- `cf.email.send()` invokes the configured email handler in `createTestContext()`-backed tests and otherwise falls back to the local email endpoint; for ingress-fidelity-sensitive flows, validate with a higher-level integration test
|
|
479
|
+
- remote mode is mainly about AI and Vectorize, not “make every binding remote”
|
|
670
480
|
|
|
671
481
|
---
|
|
672
482
|
|
|
673
483
|
## CLI
|
|
674
484
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
devflare
|
|
679
|
-
devflare
|
|
680
|
-
devflare build
|
|
681
|
-
devflare
|
|
682
|
-
devflare
|
|
683
|
-
devflare
|
|
684
|
-
devflare
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
485
|
+
| Command | What it does |
|
|
486
|
+
|---|---|
|
|
487
|
+
| `devflare init` | scaffold a project using `src/fetch.ts` and explicit `files.fetch` |
|
|
488
|
+
| `devflare dev` | start the worker-only dev server, enabling Vite only when the current package has a local `vite.config.*` |
|
|
489
|
+
| `devflare build` | resolve config, generate `wrangler.jsonc`, and run `vite build` only for Vite-backed packages |
|
|
490
|
+
| `devflare deploy` | build and deploy with Wrangler |
|
|
491
|
+
| `devflare types` | generate `env.d.ts` |
|
|
492
|
+
| `devflare doctor` | check project configuration and generated artifacts |
|
|
493
|
+
| `devflare account` | inspect accounts, resources, usage, and limits |
|
|
494
|
+
| `devflare ai` | show Workers AI model pricing info |
|
|
495
|
+
| `devflare remote` | manage remote test mode |
|
|
496
|
+
|
|
497
|
+
Useful flags:
|
|
498
|
+
|
|
499
|
+
- `build --env <name>`
|
|
500
|
+
- `deploy --env <name>`
|
|
501
|
+
- `deploy --dry-run`
|
|
502
|
+
- `types --output <path>`
|
|
503
|
+
- `doctor --config <path>`
|
|
504
|
+
- `account --account <id>`
|
|
505
|
+
|
|
506
|
+
Recommended invocation style:
|
|
688
507
|
|
|
689
508
|
```bash
|
|
690
509
|
bunx --bun devflare dev
|
|
@@ -694,44 +513,22 @@ bunx --bun devflare build
|
|
|
694
513
|
|
|
695
514
|
---
|
|
696
515
|
|
|
697
|
-
##
|
|
698
|
-
|
|
699
|
-
```text
|
|
700
|
-
.
|
|
701
|
-
├── devflare.config.ts
|
|
702
|
-
├── env.d.ts
|
|
703
|
-
├── src/
|
|
704
|
-
│ ├── fetch.ts
|
|
705
|
-
│ ├── routes/
|
|
706
|
-
│ ├── queue.ts
|
|
707
|
-
│ ├── scheduled.ts
|
|
708
|
-
│ ├── email.ts
|
|
709
|
-
│ ├── do.counter.ts
|
|
710
|
-
│ ├── ep.admin.ts
|
|
711
|
-
│ └── transport.ts
|
|
712
|
-
└── tests/
|
|
713
|
-
└── worker.test.ts
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
Not every project needs every file.
|
|
516
|
+
## Generated artifacts
|
|
717
517
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
---
|
|
518
|
+
Treat these as generated output, not source of truth:
|
|
721
519
|
|
|
722
|
-
|
|
520
|
+
- `.devflare/`
|
|
521
|
+
- `env.d.ts`
|
|
522
|
+
- generated `wrangler.jsonc`
|
|
723
523
|
|
|
724
|
-
|
|
524
|
+
The source of truth is still:
|
|
725
525
|
|
|
726
|
-
-
|
|
727
|
-
-
|
|
728
|
-
-
|
|
729
|
-
- local-first development with realistic tests
|
|
730
|
-
- browser rendering in your local workflow
|
|
731
|
-
- Vite or SvelteKit apps that still want a clean Worker model
|
|
526
|
+
- `devflare.config.ts`
|
|
527
|
+
- your source files under `src/`
|
|
528
|
+
- your tests
|
|
732
529
|
|
|
733
530
|
---
|
|
734
531
|
|
|
735
532
|
## In one sentence
|
|
736
533
|
|
|
737
|
-
**Devflare helps you build Cloudflare Workers with clearer structure, better local tooling, and a development
|
|
534
|
+
**Devflare helps you build Cloudflare Workers with clearer structure, better local tooling, and a development workflow that stays coherent as the app grows.**
|