devflare 0.0.0 → 1.0.0-next.1
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 +976 -0
- package/README.md +737 -1
- package/bin/devflare.js +14 -0
- package/dist/account-rvrj687w.js +397 -0
- package/dist/ai-dx4fr9jh.js +107 -0
- package/dist/bridge/client.d.ts +82 -0
- package/dist/bridge/client.d.ts.map +1 -0
- package/dist/bridge/index.d.ts +7 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/miniflare.d.ts +70 -0
- package/dist/bridge/miniflare.d.ts.map +1 -0
- package/dist/bridge/protocol.d.ts +146 -0
- package/dist/bridge/protocol.d.ts.map +1 -0
- package/dist/bridge/proxy.d.ts +49 -0
- package/dist/bridge/proxy.d.ts.map +1 -0
- package/dist/bridge/serialization.d.ts +83 -0
- package/dist/bridge/serialization.d.ts.map +1 -0
- package/dist/bridge/server.d.ts +8 -0
- package/dist/bridge/server.d.ts.map +1 -0
- package/dist/browser-shim/binding-worker.d.ts +7 -0
- package/dist/browser-shim/binding-worker.d.ts.map +1 -0
- package/dist/browser-shim/handler.d.ts +21 -0
- package/dist/browser-shim/handler.d.ts.map +1 -0
- package/dist/browser-shim/index.d.ts +3 -0
- package/dist/browser-shim/index.d.ts.map +1 -0
- package/dist/browser-shim/server.d.ts +25 -0
- package/dist/browser-shim/server.d.ts.map +1 -0
- package/dist/browser-shim/worker.d.ts +14 -0
- package/dist/browser-shim/worker.d.ts.map +1 -0
- package/dist/build-mnf6v8gd.js +53 -0
- package/dist/bundler/do-bundler.d.ts +42 -0
- package/dist/bundler/do-bundler.d.ts.map +1 -0
- package/dist/bundler/index.d.ts +2 -0
- package/dist/bundler/index.d.ts.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/colors.d.ts +11 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/commands/account.d.ts +4 -0
- package/dist/cli/commands/account.d.ts.map +1 -0
- package/dist/cli/commands/ai.d.ts +3 -0
- package/dist/cli/commands/ai.d.ts.map +1 -0
- package/dist/cli/commands/build.d.ts +4 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/deploy.d.ts +4 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +4 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +4 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/remote.d.ts +4 -0
- package/dist/cli/commands/remote.d.ts.map +1 -0
- package/dist/cli/commands/types.d.ts +4 -0
- package/dist/cli/commands/types.d.ts.map +1 -0
- package/dist/cli/dependencies.d.ts +90 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/index.d.ts +23 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/wrangler-auth.d.ts +36 -0
- package/dist/cli/wrangler-auth.d.ts.map +1 -0
- package/dist/cloudflare/account.d.ts +65 -0
- package/dist/cloudflare/account.d.ts.map +1 -0
- package/dist/cloudflare/api.d.ts +51 -0
- package/dist/cloudflare/api.d.ts.map +1 -0
- package/dist/cloudflare/auth.d.ts +35 -0
- package/dist/cloudflare/auth.d.ts.map +1 -0
- package/dist/cloudflare/index.d.ts +107 -0
- package/dist/cloudflare/index.d.ts.map +1 -0
- package/dist/cloudflare/index.js +13 -0
- package/dist/cloudflare/preferences.d.ts +46 -0
- package/dist/cloudflare/preferences.d.ts.map +1 -0
- package/dist/cloudflare/pricing.d.ts +15 -0
- package/dist/cloudflare/pricing.d.ts.map +1 -0
- package/dist/cloudflare/remote-config.d.ts +37 -0
- package/dist/cloudflare/remote-config.d.ts.map +1 -0
- package/dist/cloudflare/types.d.ts +161 -0
- package/dist/cloudflare/types.d.ts.map +1 -0
- package/dist/cloudflare/usage.d.ts +77 -0
- package/dist/cloudflare/usage.d.ts.map +1 -0
- package/dist/config/compiler.d.ts +146 -0
- package/dist/config/compiler.d.ts.map +1 -0
- package/dist/config/define.d.ts +44 -0
- package/dist/config/define.d.ts.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/loader.d.ts +52 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/ref.d.ts +160 -0
- package/dist/config/ref.d.ts.map +1 -0
- package/dist/config/schema.d.ts +3318 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/decorators/durable-object.d.ts +59 -0
- package/dist/decorators/durable-object.d.ts.map +1 -0
- package/dist/decorators/index.d.ts +3 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +9 -0
- package/dist/deploy-nhceck39.js +70 -0
- package/dist/dev-qnxet3j9.js +2096 -0
- package/dist/dev-server/index.d.ts +2 -0
- package/dist/dev-server/index.d.ts.map +1 -0
- package/dist/dev-server/server.d.ts +30 -0
- package/dist/dev-server/server.d.ts.map +1 -0
- package/dist/doctor-e8fy6fj5.js +186 -0
- package/dist/durable-object-t4kbb0yt.js +13 -0
- package/dist/env.d.ts +48 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/index-07q6yxyc.js +168 -0
- package/dist/index-1xpj0m4r.js +57 -0
- package/dist/index-37x76zdn.js +4 -0
- package/dist/index-3t6rypgc.js +13 -0
- package/dist/index-67qcae0f.js +183 -0
- package/dist/index-a855bdsx.js +18 -0
- package/dist/index-d8bdkx2h.js +109 -0
- package/dist/index-ep3445yc.js +2225 -0
- package/dist/index-gz1gndna.js +307 -0
- package/dist/index-hcex3rgh.js +266 -0
- package/dist/index-m2q41jwa.js +462 -0
- package/dist/index-n7rs26ft.js +77 -0
- package/dist/index-pf5s73n9.js +1413 -0
- package/dist/index-rbht7m9r.js +36 -0
- package/dist/index-tfyxa77h.js +850 -0
- package/dist/index-tk6ej9dj.js +94 -0
- package/dist/index-z14anrqp.js +226 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +298 -0
- package/dist/init-f9mgmew3.js +186 -0
- package/dist/remote-q59qk463.js +97 -0
- package/dist/runtime/context.d.ts +46 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/exports.d.ts +118 -0
- package/dist/runtime/exports.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +111 -0
- package/dist/runtime/middleware.d.ts +82 -0
- package/dist/runtime/middleware.d.ts.map +1 -0
- package/dist/runtime/validation.d.ts +37 -0
- package/dist/runtime/validation.d.ts.map +1 -0
- package/dist/sveltekit/index.d.ts +2 -0
- package/dist/sveltekit/index.d.ts.map +1 -0
- package/dist/sveltekit/index.js +182 -0
- package/dist/sveltekit/platform.d.ts +141 -0
- package/dist/sveltekit/platform.d.ts.map +1 -0
- package/dist/test/bridge-context.d.ts +73 -0
- package/dist/test/bridge-context.d.ts.map +1 -0
- package/dist/test/cf.d.ts +130 -0
- package/dist/test/cf.d.ts.map +1 -0
- package/dist/test/email.d.ts +75 -0
- package/dist/test/email.d.ts.map +1 -0
- package/dist/test/index.d.ts +22 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +71 -0
- package/dist/test/multi-worker-context.d.ts +114 -0
- package/dist/test/multi-worker-context.d.ts.map +1 -0
- package/dist/test/queue.d.ts +74 -0
- package/dist/test/queue.d.ts.map +1 -0
- package/dist/test/remote-ai.d.ts +6 -0
- package/dist/test/remote-ai.d.ts.map +1 -0
- package/dist/test/remote-vectorize.d.ts +6 -0
- package/dist/test/remote-vectorize.d.ts.map +1 -0
- package/dist/test/resolve-service-bindings.d.ts +68 -0
- package/dist/test/resolve-service-bindings.d.ts.map +1 -0
- package/dist/test/scheduled.d.ts +58 -0
- package/dist/test/scheduled.d.ts.map +1 -0
- package/dist/test/should-skip.d.ts +50 -0
- package/dist/test/should-skip.d.ts.map +1 -0
- package/dist/test/simple-context.d.ts +43 -0
- package/dist/test/simple-context.d.ts.map +1 -0
- package/dist/test/tail.d.ts +86 -0
- package/dist/test/tail.d.ts.map +1 -0
- package/dist/test/utilities.d.ts +99 -0
- package/dist/test/utilities.d.ts.map +1 -0
- package/dist/test/worker.d.ts +76 -0
- package/dist/test/worker.d.ts.map +1 -0
- package/dist/transform/durable-object.d.ts +46 -0
- package/dist/transform/durable-object.d.ts.map +1 -0
- package/dist/transform/index.d.ts +3 -0
- package/dist/transform/index.d.ts.map +1 -0
- package/dist/transform/worker-entrypoint.d.ts +66 -0
- package/dist/transform/worker-entrypoint.d.ts.map +1 -0
- package/dist/types-5nyrz1sz.js +454 -0
- package/dist/utils/entrypoint-discovery.d.ts +29 -0
- package/dist/utils/entrypoint-discovery.d.ts.map +1 -0
- package/dist/utils/glob.d.ts +33 -0
- package/dist/utils/glob.d.ts.map +1 -0
- package/dist/utils/resolve-package.d.ts +10 -0
- package/dist/utils/resolve-package.d.ts.map +1 -0
- package/dist/vite/index.d.ts +3 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +339 -0
- package/dist/vite/plugin.d.ts +138 -0
- package/dist/vite/plugin.d.ts.map +1 -0
- package/dist/worker-entrypoint-m9th0rg0.js +13 -0
- package/dist/workerName.d.ts +17 -0
- package/dist/workerName.d.ts.map +1 -0
- package/package.json +112 -1
package/README.md
CHANGED
|
@@ -1 +1,737 @@
|
|
|
1
|
-
|
|
1
|
+
# Devflare
|
|
2
|
+
|
|
3
|
+
**Build Cloudflare Workers like an application, not a pile of glue.**
|
|
4
|
+
|
|
5
|
+
Devflare is a developer-first toolkit for Cloudflare Workers that sits on top of Miniflare and Wrangler-compatible config.
|
|
6
|
+
|
|
7
|
+
It gives you:
|
|
8
|
+
|
|
9
|
+
- a clean file structure
|
|
10
|
+
- typed config with `defineConfig()`
|
|
11
|
+
- easier local development
|
|
12
|
+
- great testing ergonomics
|
|
13
|
+
- first-class Durable Object and multi-worker workflows
|
|
14
|
+
- browser rendering support in local development
|
|
15
|
+
- Vite and SvelteKit integration when your app grows up
|
|
16
|
+
|
|
17
|
+
Miniflare gives you a local runtime.
|
|
18
|
+
|
|
19
|
+
Devflare turns that runtime into a **coherent development system**.
|
|
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.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Install
|
|
87
|
+
|
|
88
|
+
For a typical project, install Devflare and Wrangler as dev dependencies:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bun add -d devflare wrangler
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If you are using Vite integration, also install the Vite plugin dependencies:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bun add -d vite @cloudflare/vite-plugin
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Quick start
|
|
103
|
+
|
|
104
|
+
### 1. Create a config
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// devflare.config.ts
|
|
108
|
+
import { defineConfig } from 'devflare'
|
|
109
|
+
|
|
110
|
+
export default defineConfig({
|
|
111
|
+
name: 'hello-worker'
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2. Add a fetch handler
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// src/fetch.ts
|
|
119
|
+
export default async function fetch(): Promise<Response> {
|
|
120
|
+
return new Response('Hello from Devflare')
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 3. Generate types
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
bunx --bun devflare types
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This creates `env.d.ts` so your bindings and entrypoints stay typed.
|
|
131
|
+
|
|
132
|
+
### 4. Start development
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bunx --bun devflare dev
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 5. Add a test
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// tests/worker.test.ts
|
|
142
|
+
import { beforeAll, afterAll, describe, expect, test } from 'bun:test'
|
|
143
|
+
import { createTestContext, cf } from 'devflare/test'
|
|
144
|
+
import { env } from 'devflare'
|
|
145
|
+
|
|
146
|
+
beforeAll(() => createTestContext())
|
|
147
|
+
afterAll(() => env.dispose())
|
|
148
|
+
|
|
149
|
+
describe('hello-worker', () => {
|
|
150
|
+
test('GET / returns text', async () => {
|
|
151
|
+
const response = await cf.worker.get('/')
|
|
152
|
+
expect(response.status).toBe(200)
|
|
153
|
+
expect(await response.text()).toBe('Hello from Devflare')
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## The Devflare way to structure an app
|
|
161
|
+
|
|
162
|
+
Devflare works best when each concern lives in its own file.
|
|
163
|
+
|
|
164
|
+
### Common files
|
|
165
|
+
|
|
166
|
+
| File | Purpose |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `src/fetch.ts` | HTTP requests |
|
|
169
|
+
| `src/queue.ts` | queue consumers |
|
|
170
|
+
| `src/scheduled.ts` | cron handlers |
|
|
171
|
+
| `src/email.ts` | email handlers |
|
|
172
|
+
| `src/routes/**` | file-based routing |
|
|
173
|
+
| `do.*.ts` | Durable Objects |
|
|
174
|
+
| `ep.*.ts` | named WorkerEntrypoints |
|
|
175
|
+
| `wf.*.ts` | workflows |
|
|
176
|
+
| `src/transport.ts` | custom serialization across boundaries |
|
|
177
|
+
|
|
178
|
+
This is more than style.
|
|
179
|
+
|
|
180
|
+
It is how Devflare helps you keep local development, testing, and runtime behavior aligned.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Example config
|
|
185
|
+
|
|
186
|
+
```ts
|
|
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
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Routing without the giant switch statement
|
|
225
|
+
|
|
226
|
+
As your HTTP surface grows, you do not need to keep stuffing logic into `fetch.ts`.
|
|
227
|
+
|
|
228
|
+
Use file-based routing instead:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { defineConfig } from 'devflare'
|
|
232
|
+
|
|
233
|
+
export default defineConfig({
|
|
234
|
+
name: 'my-api',
|
|
235
|
+
files: {
|
|
236
|
+
routes: {
|
|
237
|
+
dir: 'src/routes',
|
|
238
|
+
prefix: '/api'
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
This lets your app grow naturally while keeping each endpoint focused and easy to maintain.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Queues without extra glue
|
|
249
|
+
|
|
250
|
+
Devflare gives queues a natural place in your app:
|
|
251
|
+
|
|
252
|
+
- produce messages from `fetch` or other handlers
|
|
253
|
+
- consume them in `src/queue.ts`
|
|
254
|
+
- test them with `cf.queue`
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
// devflare.config.ts
|
|
258
|
+
import { defineConfig } from 'devflare'
|
|
259
|
+
|
|
260
|
+
export default defineConfig({
|
|
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
|
+
```
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
// src/fetch.ts
|
|
284
|
+
import { env } from 'devflare'
|
|
285
|
+
|
|
286
|
+
export default async function fetch(request: Request): Promise<Response> {
|
|
287
|
+
const url = new URL(request.url)
|
|
288
|
+
|
|
289
|
+
if (url.pathname === '/tasks') {
|
|
290
|
+
const task = {
|
|
291
|
+
id: crypto.randomUUID(),
|
|
292
|
+
type: 'resize-image'
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
await env.TASK_QUEUE.send(task)
|
|
296
|
+
return Response.json({ queued: true, task })
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return new Response('Not found', { status: 404 })
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
// src/queue.ts
|
|
305
|
+
import type { MessageBatch } from '@cloudflare/workers-types'
|
|
306
|
+
|
|
307
|
+
type Task = {
|
|
308
|
+
id: string
|
|
309
|
+
type: string
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export default async function queue(
|
|
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
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Email
|
|
333
|
+
|
|
334
|
+
Devflare supports two sides of email: **receiving** incoming messages and **sending** outgoing ones.
|
|
335
|
+
|
|
336
|
+
### Receiving email
|
|
337
|
+
|
|
338
|
+
Export an `email` function from `src/email.ts`. Devflare wires it as the worker's incoming email handler automatically.
|
|
339
|
+
|
|
340
|
+
```ts
|
|
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
|
+
)
|
|
357
|
+
|
|
358
|
+
// Forward to an admin address
|
|
359
|
+
await message.forward(env.FORWARD_ADDRESS)
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
You can also auto-reply with `message.reply(replyMessage)` — useful for sending confirmation receipts.
|
|
364
|
+
|
|
365
|
+
### Sending email
|
|
366
|
+
|
|
367
|
+
To send outgoing email, declare a `sendEmail` binding. Each key becomes an `env` property of type `SendEmail`.
|
|
368
|
+
|
|
369
|
+
The value is a config object that accepts an optional `destinationAddress`. When set, every email sent through that binding goes to that address. When omitted (`{}`), you specify the recipient at send time.
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
// devflare.config.ts
|
|
373
|
+
import { defineConfig } from 'devflare'
|
|
374
|
+
|
|
375
|
+
export default defineConfig({
|
|
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' },
|
|
384
|
+
|
|
385
|
+
// Open — you choose the recipient per message
|
|
386
|
+
EMAIL: {}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
This generates two typed bindings on `env`:
|
|
393
|
+
|
|
394
|
+
- `env.ADMIN_EMAIL` — always delivers to `admin@example.com`
|
|
395
|
+
- `env.EMAIL` — you provide the destination when composing the message
|
|
396
|
+
|
|
397
|
+
### Testing email
|
|
398
|
+
|
|
399
|
+
Import the `email` helper from `devflare/test` to send test messages to your running dev server:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import { email } from 'devflare/test'
|
|
403
|
+
|
|
404
|
+
const response = await email.send({
|
|
405
|
+
from: 'sender@example.com',
|
|
406
|
+
to: 'recipient@example.com',
|
|
407
|
+
subject: 'Hello from devflare',
|
|
408
|
+
body: 'This is a test email.'
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
expect(response.ok).toBe(true)
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Durable Objects and multi-worker setups
|
|
417
|
+
|
|
418
|
+
Devflare makes advanced Worker architecture feel much less dramatic.
|
|
419
|
+
|
|
420
|
+
### Local Durable Objects
|
|
421
|
+
|
|
422
|
+
```ts
|
|
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.
|
|
431
|
+
|
|
432
|
+
### Write a Durable Object and call it from `fetch`
|
|
433
|
+
|
|
434
|
+
This is the most common pattern:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
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
|
+
```
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
// src/do.session.ts
|
|
452
|
+
import { DurableObject } from 'cloudflare:workers'
|
|
453
|
+
|
|
454
|
+
export class SessionStore extends DurableObject<DevflareEnv> {
|
|
455
|
+
private data = new Map<string, string>()
|
|
456
|
+
|
|
457
|
+
getValue(key: string): string | null {
|
|
458
|
+
return this.data.get(key) ?? null
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
setValue(key: string, value: string): void {
|
|
462
|
+
this.data.set(key, value)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
clearAll(): void {
|
|
466
|
+
this.data.clear()
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
// src/fetch.ts
|
|
473
|
+
import { env } from 'devflare'
|
|
474
|
+
|
|
475
|
+
export default async function fetch(request: Request): Promise<Response> {
|
|
476
|
+
const url = new URL(request.url)
|
|
477
|
+
const sessionId = url.searchParams.get('session') ?? 'default'
|
|
478
|
+
|
|
479
|
+
const id = env.SESSION.idFromName(sessionId)
|
|
480
|
+
const session = env.SESSION.get(id)
|
|
481
|
+
|
|
482
|
+
if (url.pathname === '/set') {
|
|
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
|
+
}
|
|
488
|
+
|
|
489
|
+
if (url.pathname === '/get') {
|
|
490
|
+
const key = url.searchParams.get('key') ?? 'name'
|
|
491
|
+
const value = await session.getValue(key)
|
|
492
|
+
return Response.json({ value })
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (url.pathname === '/clear') {
|
|
496
|
+
await session.clearAll()
|
|
497
|
+
return Response.json({ ok: true })
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return new Response('Not found', { status: 404 })
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
The flow is simple:
|
|
505
|
+
|
|
506
|
+
1. bind the Durable Object in `devflare.config.ts`
|
|
507
|
+
2. implement the class in a `do.*.ts` file
|
|
508
|
+
3. get a stub with `env.SESSION.get(env.SESSION.idFromName(...))`
|
|
509
|
+
4. call your methods from `fetch`
|
|
510
|
+
|
|
511
|
+
### Cross-worker references
|
|
512
|
+
|
|
513
|
+
Use `ref()` when one worker depends on another worker or its entrypoints/DO bindings:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
import { defineConfig, ref } from 'devflare'
|
|
517
|
+
|
|
518
|
+
const authWorker = ref(() => import('../auth/devflare.config'))
|
|
519
|
+
|
|
520
|
+
export default defineConfig({
|
|
521
|
+
name: 'gateway',
|
|
522
|
+
bindings: {
|
|
523
|
+
services: {
|
|
524
|
+
AUTH: authWorker.worker
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Typed relationships, less duplication, fewer paper cuts.
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## Transport for custom types, without manual serialization
|
|
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.
|
|
539
|
+
|
|
540
|
+
You define how to encode and decode a custom type **once**, and Devflare applies it for you across supported boundaries.
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
// src/DoubleableNumber.ts
|
|
544
|
+
export class DoubleableNumber {
|
|
545
|
+
value: number
|
|
546
|
+
|
|
547
|
+
constructor(n: number) {
|
|
548
|
+
this.value = n
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
get double() {
|
|
552
|
+
return this.value * 2
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
// src/transport.ts
|
|
559
|
+
import { DoubleableNumber } from './DoubleableNumber'
|
|
560
|
+
|
|
561
|
+
export const transport = {
|
|
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
|
+
```
|
|
582
|
+
|
|
583
|
+
```ts
|
|
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'
|
|
589
|
+
|
|
590
|
+
beforeAll(() => createTestContext())
|
|
591
|
+
afterAll(() => env.dispose())
|
|
592
|
+
|
|
593
|
+
test('transport restores my custom type', async () => {
|
|
594
|
+
const id = env.COUNTER.idFromName('main')
|
|
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
|
+
})
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
The important bit: **you do not manually encode before returning or manually decode after receiving**.
|
|
604
|
+
|
|
605
|
+
Devflare handles the transport step so the developer works with the real type again.
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## Testing that feels like the real app
|
|
610
|
+
|
|
611
|
+
Devflare ships a clean testing API through `devflare/test`.
|
|
612
|
+
|
|
613
|
+
### Integration-style tests
|
|
614
|
+
|
|
615
|
+
Use `createTestContext()` for real local behavior backed by Devflare’s Miniflare orchestration.
|
|
616
|
+
|
|
617
|
+
### Mock-style tests
|
|
618
|
+
|
|
619
|
+
Use:
|
|
620
|
+
|
|
621
|
+
- `createMockTestContext()`
|
|
622
|
+
- `withTestContext()`
|
|
623
|
+
- `createMockKV()`
|
|
624
|
+
- `createMockD1()`
|
|
625
|
+
- `createMockR2()`
|
|
626
|
+
- `createMockQueue()`
|
|
627
|
+
|
|
628
|
+
### Unified handler helpers
|
|
629
|
+
|
|
630
|
+
You can test each surface directly:
|
|
631
|
+
|
|
632
|
+
- `cf.worker`
|
|
633
|
+
- `cf.queue`
|
|
634
|
+
- `cf.scheduled`
|
|
635
|
+
- `cf.email`
|
|
636
|
+
- `cf.tail`
|
|
637
|
+
|
|
638
|
+
That consistency is a huge quality-of-life improvement.
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## Browser rendering, locally
|
|
643
|
+
|
|
644
|
+
Browser rendering is a perfect example of Devflare adding capability above raw Miniflare.
|
|
645
|
+
|
|
646
|
+
With Devflare, browser rendering becomes part of the same local development story as the rest of your Worker bindings.
|
|
647
|
+
|
|
648
|
+
That means you can build richer Worker applications without stitching together a separate custom local browser setup by hand.
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Framework support
|
|
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
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## CLI
|
|
674
|
+
|
|
675
|
+
Devflare includes a CLI for the full development loop.
|
|
676
|
+
|
|
677
|
+
```text
|
|
678
|
+
devflare init
|
|
679
|
+
devflare dev
|
|
680
|
+
devflare build
|
|
681
|
+
devflare deploy
|
|
682
|
+
devflare types
|
|
683
|
+
devflare doctor
|
|
684
|
+
devflare remote
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Most projects will mainly use:
|
|
688
|
+
|
|
689
|
+
```bash
|
|
690
|
+
bunx --bun devflare dev
|
|
691
|
+
bunx --bun devflare types
|
|
692
|
+
bunx --bun devflare build
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## A good starter layout
|
|
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.
|
|
717
|
+
|
|
718
|
+
But if you follow this shape, Devflare will feel very natural.
|
|
719
|
+
|
|
720
|
+
---
|
|
721
|
+
|
|
722
|
+
## When to use Devflare
|
|
723
|
+
|
|
724
|
+
Devflare is especially nice when you want:
|
|
725
|
+
|
|
726
|
+
- Cloudflare Workers with clean project structure
|
|
727
|
+
- Durable Object-heavy apps
|
|
728
|
+
- multi-worker systems with service bindings
|
|
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
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## In one sentence
|
|
736
|
+
|
|
737
|
+
**Devflare helps you build Cloudflare Workers with clearer structure, better local tooling, and a development experience that scales with your app.**
|