devflare 0.0.0 → 1.0.0-next.0

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.
Files changed (198) hide show
  1. package/README.md +737 -1
  2. package/bin/devflare.js +14 -0
  3. package/dist/account-rvrj687w.js +397 -0
  4. package/dist/ai-dx4fr9jh.js +107 -0
  5. package/dist/bridge/client.d.ts +82 -0
  6. package/dist/bridge/client.d.ts.map +1 -0
  7. package/dist/bridge/index.d.ts +7 -0
  8. package/dist/bridge/index.d.ts.map +1 -0
  9. package/dist/bridge/miniflare.d.ts +70 -0
  10. package/dist/bridge/miniflare.d.ts.map +1 -0
  11. package/dist/bridge/protocol.d.ts +146 -0
  12. package/dist/bridge/protocol.d.ts.map +1 -0
  13. package/dist/bridge/proxy.d.ts +49 -0
  14. package/dist/bridge/proxy.d.ts.map +1 -0
  15. package/dist/bridge/serialization.d.ts +83 -0
  16. package/dist/bridge/serialization.d.ts.map +1 -0
  17. package/dist/bridge/server.d.ts +8 -0
  18. package/dist/bridge/server.d.ts.map +1 -0
  19. package/dist/browser-shim/binding-worker.d.ts +7 -0
  20. package/dist/browser-shim/binding-worker.d.ts.map +1 -0
  21. package/dist/browser-shim/handler.d.ts +21 -0
  22. package/dist/browser-shim/handler.d.ts.map +1 -0
  23. package/dist/browser-shim/index.d.ts +3 -0
  24. package/dist/browser-shim/index.d.ts.map +1 -0
  25. package/dist/browser-shim/server.d.ts +25 -0
  26. package/dist/browser-shim/server.d.ts.map +1 -0
  27. package/dist/browser-shim/worker.d.ts +14 -0
  28. package/dist/browser-shim/worker.d.ts.map +1 -0
  29. package/dist/build-mnf6v8gd.js +53 -0
  30. package/dist/bundler/do-bundler.d.ts +42 -0
  31. package/dist/bundler/do-bundler.d.ts.map +1 -0
  32. package/dist/bundler/index.d.ts +2 -0
  33. package/dist/bundler/index.d.ts.map +1 -0
  34. package/dist/cli/bin.d.ts +3 -0
  35. package/dist/cli/bin.d.ts.map +1 -0
  36. package/dist/cli/colors.d.ts +11 -0
  37. package/dist/cli/colors.d.ts.map +1 -0
  38. package/dist/cli/commands/account.d.ts +4 -0
  39. package/dist/cli/commands/account.d.ts.map +1 -0
  40. package/dist/cli/commands/ai.d.ts +3 -0
  41. package/dist/cli/commands/ai.d.ts.map +1 -0
  42. package/dist/cli/commands/build.d.ts +4 -0
  43. package/dist/cli/commands/build.d.ts.map +1 -0
  44. package/dist/cli/commands/deploy.d.ts +4 -0
  45. package/dist/cli/commands/deploy.d.ts.map +1 -0
  46. package/dist/cli/commands/dev.d.ts +4 -0
  47. package/dist/cli/commands/dev.d.ts.map +1 -0
  48. package/dist/cli/commands/doctor.d.ts +4 -0
  49. package/dist/cli/commands/doctor.d.ts.map +1 -0
  50. package/dist/cli/commands/init.d.ts +4 -0
  51. package/dist/cli/commands/init.d.ts.map +1 -0
  52. package/dist/cli/commands/remote.d.ts +4 -0
  53. package/dist/cli/commands/remote.d.ts.map +1 -0
  54. package/dist/cli/commands/types.d.ts +4 -0
  55. package/dist/cli/commands/types.d.ts.map +1 -0
  56. package/dist/cli/dependencies.d.ts +90 -0
  57. package/dist/cli/dependencies.d.ts.map +1 -0
  58. package/dist/cli/index.d.ts +23 -0
  59. package/dist/cli/index.d.ts.map +1 -0
  60. package/dist/cli/wrangler-auth.d.ts +36 -0
  61. package/dist/cli/wrangler-auth.d.ts.map +1 -0
  62. package/dist/cloudflare/account.d.ts +65 -0
  63. package/dist/cloudflare/account.d.ts.map +1 -0
  64. package/dist/cloudflare/api.d.ts +51 -0
  65. package/dist/cloudflare/api.d.ts.map +1 -0
  66. package/dist/cloudflare/auth.d.ts +35 -0
  67. package/dist/cloudflare/auth.d.ts.map +1 -0
  68. package/dist/cloudflare/index.d.ts +107 -0
  69. package/dist/cloudflare/index.d.ts.map +1 -0
  70. package/dist/cloudflare/index.js +13 -0
  71. package/dist/cloudflare/preferences.d.ts +46 -0
  72. package/dist/cloudflare/preferences.d.ts.map +1 -0
  73. package/dist/cloudflare/pricing.d.ts +15 -0
  74. package/dist/cloudflare/pricing.d.ts.map +1 -0
  75. package/dist/cloudflare/remote-config.d.ts +37 -0
  76. package/dist/cloudflare/remote-config.d.ts.map +1 -0
  77. package/dist/cloudflare/types.d.ts +161 -0
  78. package/dist/cloudflare/types.d.ts.map +1 -0
  79. package/dist/cloudflare/usage.d.ts +77 -0
  80. package/dist/cloudflare/usage.d.ts.map +1 -0
  81. package/dist/config/compiler.d.ts +146 -0
  82. package/dist/config/compiler.d.ts.map +1 -0
  83. package/dist/config/define.d.ts +44 -0
  84. package/dist/config/define.d.ts.map +1 -0
  85. package/dist/config/index.d.ts +6 -0
  86. package/dist/config/index.d.ts.map +1 -0
  87. package/dist/config/loader.d.ts +52 -0
  88. package/dist/config/loader.d.ts.map +1 -0
  89. package/dist/config/ref.d.ts +160 -0
  90. package/dist/config/ref.d.ts.map +1 -0
  91. package/dist/config/schema.d.ts +3318 -0
  92. package/dist/config/schema.d.ts.map +1 -0
  93. package/dist/decorators/durable-object.d.ts +59 -0
  94. package/dist/decorators/durable-object.d.ts.map +1 -0
  95. package/dist/decorators/index.d.ts +3 -0
  96. package/dist/decorators/index.d.ts.map +1 -0
  97. package/dist/decorators/index.js +9 -0
  98. package/dist/deploy-nhceck39.js +70 -0
  99. package/dist/dev-qnxet3j9.js +2096 -0
  100. package/dist/dev-server/index.d.ts +2 -0
  101. package/dist/dev-server/index.d.ts.map +1 -0
  102. package/dist/dev-server/server.d.ts +30 -0
  103. package/dist/dev-server/server.d.ts.map +1 -0
  104. package/dist/doctor-e8fy6fj5.js +186 -0
  105. package/dist/durable-object-t4kbb0yt.js +13 -0
  106. package/dist/env.d.ts +48 -0
  107. package/dist/env.d.ts.map +1 -0
  108. package/dist/index-07q6yxyc.js +168 -0
  109. package/dist/index-1xpj0m4r.js +57 -0
  110. package/dist/index-37x76zdn.js +4 -0
  111. package/dist/index-3t6rypgc.js +13 -0
  112. package/dist/index-67qcae0f.js +183 -0
  113. package/dist/index-a855bdsx.js +18 -0
  114. package/dist/index-d8bdkx2h.js +109 -0
  115. package/dist/index-ep3445yc.js +2225 -0
  116. package/dist/index-gz1gndna.js +307 -0
  117. package/dist/index-hcex3rgh.js +266 -0
  118. package/dist/index-m2q41jwa.js +462 -0
  119. package/dist/index-n7rs26ft.js +77 -0
  120. package/dist/index-pf5s73n9.js +1413 -0
  121. package/dist/index-rbht7m9r.js +36 -0
  122. package/dist/index-tfyxa77h.js +850 -0
  123. package/dist/index-tk6ej9dj.js +94 -0
  124. package/dist/index-z14anrqp.js +226 -0
  125. package/dist/index.d.ts +13 -0
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/index.js +298 -0
  128. package/dist/init-f9mgmew3.js +186 -0
  129. package/dist/remote-q59qk463.js +97 -0
  130. package/dist/runtime/context.d.ts +46 -0
  131. package/dist/runtime/context.d.ts.map +1 -0
  132. package/dist/runtime/exports.d.ts +118 -0
  133. package/dist/runtime/exports.d.ts.map +1 -0
  134. package/dist/runtime/index.d.ts +4 -0
  135. package/dist/runtime/index.d.ts.map +1 -0
  136. package/dist/runtime/index.js +111 -0
  137. package/dist/runtime/middleware.d.ts +82 -0
  138. package/dist/runtime/middleware.d.ts.map +1 -0
  139. package/dist/runtime/validation.d.ts +37 -0
  140. package/dist/runtime/validation.d.ts.map +1 -0
  141. package/dist/sveltekit/index.d.ts +2 -0
  142. package/dist/sveltekit/index.d.ts.map +1 -0
  143. package/dist/sveltekit/index.js +182 -0
  144. package/dist/sveltekit/platform.d.ts +141 -0
  145. package/dist/sveltekit/platform.d.ts.map +1 -0
  146. package/dist/test/bridge-context.d.ts +73 -0
  147. package/dist/test/bridge-context.d.ts.map +1 -0
  148. package/dist/test/cf.d.ts +130 -0
  149. package/dist/test/cf.d.ts.map +1 -0
  150. package/dist/test/email.d.ts +75 -0
  151. package/dist/test/email.d.ts.map +1 -0
  152. package/dist/test/index.d.ts +22 -0
  153. package/dist/test/index.d.ts.map +1 -0
  154. package/dist/test/index.js +71 -0
  155. package/dist/test/multi-worker-context.d.ts +114 -0
  156. package/dist/test/multi-worker-context.d.ts.map +1 -0
  157. package/dist/test/queue.d.ts +74 -0
  158. package/dist/test/queue.d.ts.map +1 -0
  159. package/dist/test/remote-ai.d.ts +6 -0
  160. package/dist/test/remote-ai.d.ts.map +1 -0
  161. package/dist/test/remote-vectorize.d.ts +6 -0
  162. package/dist/test/remote-vectorize.d.ts.map +1 -0
  163. package/dist/test/resolve-service-bindings.d.ts +68 -0
  164. package/dist/test/resolve-service-bindings.d.ts.map +1 -0
  165. package/dist/test/scheduled.d.ts +58 -0
  166. package/dist/test/scheduled.d.ts.map +1 -0
  167. package/dist/test/should-skip.d.ts +50 -0
  168. package/dist/test/should-skip.d.ts.map +1 -0
  169. package/dist/test/simple-context.d.ts +43 -0
  170. package/dist/test/simple-context.d.ts.map +1 -0
  171. package/dist/test/tail.d.ts +86 -0
  172. package/dist/test/tail.d.ts.map +1 -0
  173. package/dist/test/utilities.d.ts +99 -0
  174. package/dist/test/utilities.d.ts.map +1 -0
  175. package/dist/test/worker.d.ts +76 -0
  176. package/dist/test/worker.d.ts.map +1 -0
  177. package/dist/transform/durable-object.d.ts +46 -0
  178. package/dist/transform/durable-object.d.ts.map +1 -0
  179. package/dist/transform/index.d.ts +3 -0
  180. package/dist/transform/index.d.ts.map +1 -0
  181. package/dist/transform/worker-entrypoint.d.ts +66 -0
  182. package/dist/transform/worker-entrypoint.d.ts.map +1 -0
  183. package/dist/types-5nyrz1sz.js +454 -0
  184. package/dist/utils/entrypoint-discovery.d.ts +29 -0
  185. package/dist/utils/entrypoint-discovery.d.ts.map +1 -0
  186. package/dist/utils/glob.d.ts +33 -0
  187. package/dist/utils/glob.d.ts.map +1 -0
  188. package/dist/utils/resolve-package.d.ts +10 -0
  189. package/dist/utils/resolve-package.d.ts.map +1 -0
  190. package/dist/vite/index.d.ts +3 -0
  191. package/dist/vite/index.d.ts.map +1 -0
  192. package/dist/vite/index.js +339 -0
  193. package/dist/vite/plugin.d.ts +138 -0
  194. package/dist/vite/plugin.d.ts.map +1 -0
  195. package/dist/worker-entrypoint-m9th0rg0.js +13 -0
  196. package/dist/workerName.d.ts +17 -0
  197. package/dist/workerName.d.ts.map +1 -0
  198. package/package.json +111 -1
package/README.md CHANGED
@@ -1 +1,737 @@
1
- Something great is being built here✨
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.**