devflare 1.0.0-next.1 → 1.0.0-next.2

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