@velajs/cloudflare 1.10.0 → 1.22.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 (117) hide show
  1. package/CHANGELOG.md +33 -3
  2. package/LICENSE +21 -0
  3. package/README.md +197 -322
  4. package/dist/durable-objects.d.ts +28 -0
  5. package/dist/durable-objects.js +494 -0
  6. package/dist/durable-objects.js.map +1 -0
  7. package/dist/index.d.ts +622 -41
  8. package/dist/index.js +1023 -39
  9. package/dist/index.js.map +1 -0
  10. package/dist/nonce-validation-LE0vFk-7.js +445 -0
  11. package/dist/nonce-validation-LE0vFk-7.js.map +1 -0
  12. package/dist/nonce.durable-object-Df3_42Sy.d.ts +141 -0
  13. package/package.json +66 -44
  14. package/dist/binding-ref.d.ts +0 -8
  15. package/dist/binding-ref.js +0 -19
  16. package/dist/cloudflare-application.d.ts +0 -125
  17. package/dist/cloudflare-application.js +0 -186
  18. package/dist/cloudflare-factory.d.ts +0 -88
  19. package/dist/cloudflare-factory.js +0 -107
  20. package/dist/decorators/env.d.ts +0 -16
  21. package/dist/decorators/env.js +0 -22
  22. package/dist/decorators/queue-consumer.d.ts +0 -23
  23. package/dist/decorators/queue-consumer.js +0 -40
  24. package/dist/decorators/scheduled.d.ts +0 -20
  25. package/dist/decorators/scheduled.js +0 -36
  26. package/dist/env-ref.d.ts +0 -10
  27. package/dist/env-ref.js +0 -13
  28. package/dist/modules/ai.module.d.ts +0 -1
  29. package/dist/modules/ai.module.js +0 -8
  30. package/dist/modules/create-binding-module.d.ts +0 -14
  31. package/dist/modules/create-binding-module.js +0 -42
  32. package/dist/modules/d1.module.d.ts +0 -1
  33. package/dist/modules/d1.module.js +0 -8
  34. package/dist/modules/durable-object.module.d.ts +0 -1
  35. package/dist/modules/durable-object.module.js +0 -8
  36. package/dist/modules/env.module.d.ts +0 -17
  37. package/dist/modules/env.module.js +0 -35
  38. package/dist/modules/hyperdrive.module.d.ts +0 -1
  39. package/dist/modules/hyperdrive.module.js +0 -8
  40. package/dist/modules/kv.module.d.ts +0 -1
  41. package/dist/modules/kv.module.js +0 -8
  42. package/dist/modules/queue.module.d.ts +0 -1
  43. package/dist/modules/queue.module.js +0 -8
  44. package/dist/modules/r2.module.d.ts +0 -1
  45. package/dist/modules/r2.module.js +0 -8
  46. package/dist/modules/vectorize.module.d.ts +0 -1
  47. package/dist/modules/vectorize.module.js +0 -8
  48. package/dist/services/ai.service.d.ts +0 -6
  49. package/dist/services/ai.service.js +0 -33
  50. package/dist/services/d1.service.d.ts +0 -6
  51. package/dist/services/d1.service.js +0 -33
  52. package/dist/services/durable-object.service.d.ts +0 -6
  53. package/dist/services/durable-object.service.js +0 -33
  54. package/dist/services/env.service.d.ts +0 -31
  55. package/dist/services/env.service.js +0 -36
  56. package/dist/services/flagship-flag.driver.d.ts +0 -57
  57. package/dist/services/flagship-flag.driver.js +0 -49
  58. package/dist/services/hyperdrive.service.d.ts +0 -12
  59. package/dist/services/hyperdrive.service.js +0 -51
  60. package/dist/services/kv-cache.store.d.ts +0 -19
  61. package/dist/services/kv-cache.store.js +0 -58
  62. package/dist/services/kv-flag.driver.d.ts +0 -48
  63. package/dist/services/kv-flag.driver.js +0 -62
  64. package/dist/services/kv.service.d.ts +0 -11
  65. package/dist/services/kv.service.js +0 -33
  66. package/dist/services/queue.service.d.ts +0 -6
  67. package/dist/services/queue.service.js +0 -33
  68. package/dist/services/r2.service.d.ts +0 -6
  69. package/dist/services/r2.service.js +0 -33
  70. package/dist/services/vectorize.service.d.ts +0 -6
  71. package/dist/services/vectorize.service.js +0 -33
  72. package/dist/storage/index.d.ts +0 -7
  73. package/dist/storage/index.js +0 -6
  74. package/dist/storage/r2-storage.driver.d.ts +0 -19
  75. package/dist/storage/r2-storage.driver.js +0 -61
  76. package/dist/storage/storage-manager.service.d.ts +0 -16
  77. package/dist/storage/storage-manager.service.js +0 -56
  78. package/dist/storage/storage.controller.d.ts +0 -16
  79. package/dist/storage/storage.controller.js +0 -88
  80. package/dist/storage/storage.module.d.ts +0 -7
  81. package/dist/storage/storage.module.js +0 -37
  82. package/dist/storage/storage.service.d.ts +0 -20
  83. package/dist/storage/storage.service.js +0 -78
  84. package/dist/storage/storage.tokens.d.ts +0 -3
  85. package/dist/storage/storage.tokens.js +0 -2
  86. package/dist/storage/storage.types.d.ts +0 -17
  87. package/dist/storage/storage.types.js +0 -1
  88. package/dist/tokens.d.ts +0 -12
  89. package/dist/tokens.js +0 -12
  90. package/dist/types.d.ts +0 -13
  91. package/dist/types.js +0 -1
  92. package/dist/websocket/broadcast.d.ts +0 -15
  93. package/dist/websocket/broadcast.js +0 -26
  94. package/dist/websocket/cf-room-registry.d.ts +0 -23
  95. package/dist/websocket/cf-room-registry.js +0 -65
  96. package/dist/websocket/cf-ws-client.d.ts +0 -28
  97. package/dist/websocket/cf-ws-client.js +0 -83
  98. package/dist/websocket/cloudflare-websocket.module.d.ts +0 -11
  99. package/dist/websocket/cloudflare-websocket.module.js +0 -27
  100. package/dist/websocket/do-bootstrap.d.ts +0 -24
  101. package/dist/websocket/do-bootstrap.js +0 -55
  102. package/dist/websocket/do-live.d.ts +0 -81
  103. package/dist/websocket/do-live.js +0 -197
  104. package/dist/websocket/do-state.d.ts +0 -35
  105. package/dist/websocket/do-state.js +0 -4
  106. package/dist/websocket/do-websocket-host.d.ts +0 -28
  107. package/dist/websocket/do-websocket-host.js +0 -69
  108. package/dist/websocket/index.d.ts +0 -15
  109. package/dist/websocket/index.js +0 -15
  110. package/dist/websocket/room-id.d.ts +0 -6
  111. package/dist/websocket/room-id.js +0 -12
  112. package/dist/websocket/websocket-routing.d.ts +0 -14
  113. package/dist/websocket/websocket-routing.js +0 -45
  114. package/dist/websocket/websocket.durable-object.d.ts +0 -15
  115. package/dist/websocket/websocket.durable-object.js +0 -83
  116. package/dist/websocket/ws-server-holder.d.ts +0 -16
  117. package/dist/websocket/ws-server-holder.js +0 -34
package/CHANGELOG.md CHANGED
@@ -1,10 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Continue the coordinated framework release on the 1.x line with the current checked provider, endpoint, identity, CRUD, live, and Studio APIs. Breaking API changes are accepted during this development phase; maintained applications use the current contracts.
8
+ - Publish from the pnpm packages workspace with TypeScript 7 and GitHub OIDC. Obsolete standalone examples have been removed; runnable applications live in apps/.
9
+ - Updated workspace dependencies
10
+ - @velajs/feature-flags@1.22.0
11
+ - @velajs/vela@1.22.0
12
+
13
+ ## 2.0.1
14
+
15
+ ### Patch Changes
16
+
17
+ - Publish from the unified packages workspace with corrected repository paths, shared native tooling, TypeScript 7 checks, and npm OIDC releases. Runnable examples now live in apps/.
18
+ - Updated dependencies
19
+ - @velajs/vela@2.0.1
20
+ - @velajs/feature-flags@2.0.1
21
+
22
+ ## 2.0.0
23
+
24
+ Native binding tokens, per-environment application lifetime, environment-created module graphs, isolated live drivers, and Durable Object live inspection. Includes a complete D1/auth/CRUD/live/Studio starter.
25
+
26
+ Requires the coordinated Vela 2.0 package set. See the workspace migration guide.
27
+
28
+ ## 1.10.1
29
+
30
+ ### Patch Changes
31
+
32
+ - 6f04d15: Modernize the package build, validation, and release toolchain.
33
+
3
34
  ## 1.7.0 (2026-07-04)
4
35
 
5
36
  - `cloudflareAdapter()` exported (createCloudflareApp composes vela RuntimeAdapter); `@QueueConsumer`/`@Scheduled` declare open entrypoint kinds; queue/scheduled dispatch runs per-event in a request scope through PipelineRunner (consumer-scoped guards/interceptors/filters; request-scoped deps rebuild per batch); DO WebSocket reads `app.entrypoints`. Requires `@velajs/vela >=1.11.0`.
6
37
 
7
-
8
38
  ## 1.6.0 (2026-07-01)
9
39
 
10
40
  ### Added
@@ -25,11 +55,11 @@
25
55
 
26
56
  ```ts
27
57
  // before
28
- import { CloudflareFactory } from '@velajs/cloudflare';
58
+ import { CloudflareFactory } from "@velajs/cloudflare";
29
59
  const app = await CloudflareFactory.create(AppModule);
30
60
 
31
61
  // after
32
- import { createCloudflareApp } from '@velajs/cloudflare';
62
+ import { createCloudflareApp } from "@velajs/cloudflare";
33
63
  const app = await createCloudflareApp(AppModule);
34
64
  ```
35
65
 
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ksh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,385 +1,260 @@
1
1
  # @velajs/cloudflare
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@velajs/cloudflare)](https://www.npmjs.com/package/@velajs/cloudflare)
4
- [![CI](https://github.com/velajs/cloudflare/actions/workflows/ci.yml/badge.svg)](https://github.com/velajs/cloudflare/actions/workflows/ci.yml)
5
- [![License: MIT](https://img.shields.io/npm/l/@velajs/cloudflare)](https://github.com/velajs/cloudflare/blob/main/LICENSE)
3
+ Nest-style modules, dependency injection, controllers, queue consumers, cron triggers,
4
+ and live WebSockets on Cloudflare Workers. HTTP routing uses Hono. Bindings use the
5
+ platform's native types.
6
6
 
7
- Cloudflare Workers integration for the [Vela](https://github.com/velajs/vela) framework. NestJS-style per-service modules for KV, D1, R2, Queues, Durable Objects, Workers AI, Vectorize, and Hyperdrive.
7
+ ## Native environment and application lifetime
8
8
 
9
- ## Install
10
-
11
- ```bash
12
- pnpm add @velajs/cloudflare @velajs/vela hono
13
- pnpm add -D @cloudflare/workers-types
14
- ```
15
-
16
- `@cloudflare/workers-types` is a required peer (types-only — zero runtime cost). It's what gives `KVNamespace`, `D1Database`, `R2Bucket`, `Queue`, `DurableObjectNamespace`, `Ai`, `VectorizeIndex`, and `Hyperdrive` their proper types when you reach into the underlying binding.
17
-
18
- ## Service shape
19
-
20
- Every service is a thin typed wrapper around its Cloudflare binding. Use the accessor (`.namespace`, `.database`, `.bucket`, `.queue`, `.binding`, `.index`) to call the binding's methods directly — full `@cloudflare/workers-types` autocomplete, no shim layer in between.
21
-
22
- ```ts
23
- class KVService { readonly namespace: KVNamespace; }
24
- class D1Service { readonly database: D1Database; }
25
- class R2Service { readonly bucket: R2Bucket; }
26
- class QueueService<T> { readonly queue: Queue<T>; }
27
- class DurableObjectService { readonly namespace: DurableObjectNamespace; }
28
- class AIService { readonly binding: Ai; }
29
- class VectorizeService { readonly index: VectorizeIndex; }
30
- class HyperdriveService { readonly binding: Hyperdrive; }
31
- ```
32
-
33
- ## Quick Start
9
+ Define one typed token for your generated Workers environment. Inject that token
10
+ wherever bindings or secrets are needed, including async provider factories.
34
11
 
35
12
  ```ts
36
- import { Controller, Get, Module, Injectable, Param } from '@velajs/vela';
37
- import { createCloudflareApp, KVModule, KVService, D1Module, D1Service } from '@velajs/cloudflare';
38
-
39
- @Injectable()
40
- class UserService {
41
- constructor(
42
- private kv: KVService,
43
- private d1: D1Service,
44
- ) {}
45
-
46
- async findById(id: string) {
47
- const cached = await this.kv.namespace.get(`user:${id}`);
48
- if (cached) return JSON.parse(cached as string);
49
-
50
- const user = await this.d1.database.prepare('SELECT * FROM users WHERE id = ?').bind(id).first();
51
- if (user) await this.kv.namespace.put(`user:${id}`, JSON.stringify(user));
52
- return user;
53
- }
13
+ import { Controller, Get, Inject, InjectionToken, Module } from '@velajs/vela';
14
+ import { createCloudflareWorker } from '@velajs/cloudflare';
15
+
16
+ interface WorkerEnv {
17
+ CACHE: KVNamespace;
18
+ DB: D1Database;
19
+ FILES: R2Bucket;
20
+ JOBS: Queue<{ taskId: string }>;
21
+ SERVICE_NAME: string;
22
+ APP_SECRET: string;
54
23
  }
24
+ export const ENV = new InjectionToken<WorkerEnv>('Worker environment');
55
25
 
56
- @Controller('/users')
57
- class UserController {
58
- constructor(private users: UserService) {}
26
+ @Controller('/status')
27
+ class StatusController {
28
+ constructor(@Inject(ENV) private readonly env: WorkerEnv) {}
59
29
 
60
- @Get('/:id')
61
- async getUser(@Param('id') id: string) {
62
- return this.users.findById(id);
30
+ @Get()
31
+ async status() {
32
+ return { message: await this.env.CACHE.get('status') };
63
33
  }
64
34
  }
65
35
 
66
- @Module({
67
- imports: [
68
- KVModule.forRoot({ binding: 'CACHE' }),
69
- D1Module.forRoot({ binding: 'DB' }),
70
- ],
71
- providers: [UserService],
72
- controllers: [UserController],
73
- })
36
+ @Module({ controllers: [StatusController] })
74
37
  class AppModule {}
75
38
 
76
- export default await createCloudflareApp(AppModule);
39
+ export default createCloudflareWorker(AppModule, { envToken: ENV });
77
40
  ```
78
41
 
79
- The `binding` string matches the binding name in your `wrangler.toml`:
42
+ The worker exposes `fetch`, `queue`, and `scheduled`. Its first event builds an
43
+ application with that event's environment. Concurrent events for the same
44
+ environment object share construction. Different environment objects receive
45
+ separate applications, including separate providers, lifecycle state, and live
46
+ drivers. A failed construction is evicted and the next event retries.
80
47
 
81
- ```toml
82
- [[kv_namespaces]]
83
- binding = "CACHE"
84
- id = "abc123"
48
+ When module configuration itself needs bindings, pass `{ create: (env) => AppModule }`
49
+ instead of a static class. The callback receives the native environment inferred
50
+ from `envToken` and runs once per successful environment bootstrap. The same form
51
+ works with `VelaWebSocketDurableObject` for authenticated live gateways. See the
52
+ [complete API starter](../../apps/api-starter/README.md) for D1, Better Auth, CRUD,
53
+ the generated Hono client, live updates, and Studio inspection in one application.
85
54
 
86
- [[d1_databases]]
87
- binding = "DB"
88
- database_id = "def456"
89
- ```
55
+ The cache uses weak object keys: it does not permanently retain replaced
56
+ environments or secrets. Providers with request scope still rebuild per HTTP
57
+ request or queue/cron dispatch. Do not retain request objects or authentication
58
+ state in singleton providers.
90
59
 
91
- ## Modules
92
-
93
- Each module follows the same pattern: `XModule.forRoot({ binding: 'NAME' })` returns a dynamic module that provides a service wrapping the Cloudflare binding.
94
-
95
- | Module | Service | Cloudflare Binding |
96
- |--------|---------|-------------------|
97
- | `KVModule` | `KVService` | KV Namespace |
98
- | `D1Module` | `D1Service` | D1 Database |
99
- | `R2Module` | `R2Service` | R2 Bucket |
100
- | `QueueModule` | `QueueService` | Queue (producer) |
101
- | `DurableObjectModule` | `DurableObjectService` | Durable Object Namespace |
102
- | `AIModule` | `AIService` | Workers AI |
103
- | `VectorizeModule` | `VectorizeService` | Vectorize Index |
104
- | `HyperdriveModule` | `HyperdriveService` | Hyperdrive |
105
-
106
- ### KVModule
60
+ For explicit construction inside a platform event:
107
61
 
108
62
  ```ts
109
- import { KVModule, KVService } from '@velajs/cloudflare';
110
-
111
- @Module({ imports: [KVModule.forRoot({ binding: 'MY_KV' })] })
112
- class AppModule {}
113
-
114
- @Injectable()
115
- class CacheService {
116
- constructor(private kv: KVService) {}
117
-
118
- async get(key: string) { return this.kv.namespace.get(key); }
119
- async set(key: string, value: string) { return this.kv.namespace.put(key, value); }
120
- async remove(key: string) { return this.kv.namespace.delete(key); }
121
- async keys() { return this.kv.namespace.list(); }
122
- }
63
+ const app = await createCloudflareApp(AppModule, {
64
+ env,
65
+ envToken: ENV,
66
+ globalPrefix: '/api',
67
+ middleware: (bindings) => [async (context, next) => {
68
+ context.header('x-service', bindings.SERVICE_NAME);
69
+ await next();
70
+ }],
71
+ });
72
+ const bindings = app.get(ENV); // WorkerEnv, inferred from ENV
73
+ return app.fetch(request, env, executionContext);
123
74
  ```
124
75
 
125
- ### D1Module
126
-
127
- ```ts
128
- import { D1Module, D1Service } from '@velajs/cloudflare';
129
-
130
- @Module({ imports: [D1Module.forRoot({ binding: 'DB' })] })
131
- class AppModule {}
132
-
133
- @Injectable()
134
- class PostService {
135
- constructor(private d1: D1Service) {}
76
+ `env` is registered before provider factories and lifecycle hooks. Referencing a
77
+ binding inside `middleware(env)` is typed from that same token; request callbacks
78
+ capture the native environment without retyping Hono's context. Referencing a
79
+ binding is safe during construction; platform I/O must still happen inside a
80
+ Workers event or Durable Object context. An explicitly built application rejects
81
+ requests or events carrying another environment object, including calls through
82
+ the underlying Hono app. Internal `ctx.run` reentry retains the application's
83
+ environment.
136
84
 
137
- async findAll() {
138
- return this.d1.database.prepare('SELECT * FROM posts').all();
139
- }
140
-
141
- async create(title: string) {
142
- return this.d1.database.prepare('INSERT INTO posts (title) VALUES (?)').bind(title).run();
143
- }
144
- }
145
- ```
146
-
147
- ### R2Module
148
-
149
- ```ts
150
- import { R2Module, R2Service } from '@velajs/cloudflare';
151
-
152
- @Module({ imports: [R2Module.forRoot({ binding: 'ASSETS' })] })
153
- class AppModule {}
154
-
155
- @Injectable()
156
- class StorageService {
157
- constructor(private r2: R2Service) {}
158
-
159
- async upload(key: string, data: string) { return this.r2.bucket.put(key, data); }
160
- async download(key: string) { return this.r2.bucket.get(key); }
161
- async remove(key: string) { return this.r2.bucket.delete(key); }
162
- }
163
- ```
164
-
165
- ### QueueModule
166
-
167
- ```ts
168
- import { QueueModule, QueueService } from '@velajs/cloudflare';
169
-
170
- @Module({ imports: [QueueModule.forRoot({ binding: 'EMAIL_QUEUE' })] })
171
- class AppModule {}
172
-
173
- @Injectable()
174
- class NotificationService {
175
- constructor(private queue: QueueService) {}
176
-
177
- async sendEmail(to: string, subject: string) {
178
- await this.queue.queue.send({ to, subject });
179
- }
180
- }
181
- ```
182
-
183
- ### DurableObjectModule
184
-
185
- ```ts
186
- import { DurableObjectModule, DurableObjectService } from '@velajs/cloudflare';
187
-
188
- @Module({ imports: [DurableObjectModule.forRoot({ binding: 'COUNTER' })] })
189
- class AppModule {}
190
-
191
- @Injectable()
192
- class CounterService {
193
- constructor(private doNs: DurableObjectService) {}
85
+ `cloudflareAdapter({ env, envToken })` provides the same bootstrap and request
86
+ contract when composing `VelaFactory.create` directly.
194
87
 
195
- async increment(name: string) {
196
- const id = this.doNs.namespace.idFromName(name);
197
- const stub = this.doNs.namespace.get(id);
198
- return (stub as any).fetch('/increment');
199
- }
200
- }
201
- ```
88
+ ## Typed provider factories
202
89
 
203
- ### AIModule
90
+ Bindings retain their full native API and generic parameters. There are no
91
+ binding-name wrappers to initialize or cast.
204
92
 
205
93
  ```ts
206
- import { AIModule, AIService } from '@velajs/cloudflare';
94
+ import { defineProvider, InjectionToken, Module } from '@velajs/vela';
207
95
 
208
- @Module({ imports: [AIModule.forRoot({ binding: 'AI' })] })
209
- class AppModule {}
96
+ const TASK_QUEUE = new InjectionToken<Queue<{ taskId: string }>>('task queue');
210
97
 
211
- @Injectable()
212
- class ChatService {
213
- constructor(private ai: AIService) {}
214
-
215
- async chat(prompt: string) {
216
- return this.ai.binding.run('@cf/meta/llama-3.1-8b-instruct', {
217
- messages: [{ role: 'user', content: prompt }],
218
- });
219
- }
220
- }
98
+ @Module({
99
+ providers: [defineProvider(TASK_QUEUE, {
100
+ inject: [ENV],
101
+ useFactory: (env) => env.JOBS,
102
+ })],
103
+ exports: [TASK_QUEUE],
104
+ })
105
+ class JobsModule {}
221
106
  ```
222
107
 
223
- ### VectorizeModule
224
-
225
- ```ts
226
- import { VectorizeModule, VectorizeService } from '@velajs/cloudflare';
227
-
228
- @Module({ imports: [VectorizeModule.forRoot({ binding: 'EMBEDDINGS' })] })
229
- class AppModule {}
108
+ Every `useFactory` strategy declares its dependencies with `inject`, including
109
+ `inject: []` for factories without dependencies. This also applies to
110
+ `lazyProvider` and `forRootAsync` factory options.
230
111
 
231
- @Injectable()
232
- class SearchService {
233
- constructor(private vectorize: VectorizeService) {}
112
+ Use native `env.DB`, `env.CACHE`, `env.FILES`, `env.JOBS`, `env.AI`,
113
+ `env.VECTORIZE`, or `env.HYPERDRIVE` directly. `@Env()` remains available for HTTP
114
+ handler parameters; typed token injection also works outside HTTP.
234
115
 
235
- async search(vector: number[]) {
236
- return this.vectorize.index.query(vector, { topK: 10 });
237
- }
238
-
239
- async addVectors(vectors: unknown[]) {
240
- return this.vectorize.index.upsert(vectors);
241
- }
242
- }
243
- ```
244
-
245
- ### HyperdriveModule
116
+ ## Queues and cron
246
117
 
247
118
  ```ts
248
- import { HyperdriveModule, HyperdriveService } from '@velajs/cloudflare';
249
-
250
- @Module({ imports: [HyperdriveModule.forRoot({ binding: 'POSTGRES' })] })
251
- class AppModule {}
119
+ import { Inject, Injectable } from '@velajs/vela';
120
+ import { QueueConsumer, Scheduled } from '@velajs/cloudflare';
252
121
 
253
122
  @Injectable()
254
- class DbService {
255
- constructor(private hd: HyperdriveService) {}
123
+ class Jobs {
124
+ constructor(@Inject(ENV) private readonly env: WorkerEnv) {}
256
125
 
257
- getConnectionString() {
258
- return this.hd.binding.connectionString;
126
+ @Scheduled('0 * * * *')
127
+ async refresh() {
128
+ await this.env.CACHE.put('last-refresh', new Date().toISOString());
259
129
  }
260
130
 
261
- getConfig() {
262
- return { host: this.hd.binding.host, port: this.hd.binding.port, database: this.hd.binding.database };
131
+ @QueueConsumer('jobs')
132
+ async consume(batch: MessageBatch<unknown>) {
133
+ for (const message of batch.messages) {
134
+ // Validate message.body before interpreting its application shape.
135
+ }
263
136
  }
264
137
  }
265
138
  ```
266
139
 
267
- ## Decorators
140
+ Core `@Cron()` also runs on Workers scheduled triggers. Consumers use fresh
141
+ request scopes and their declared guards, interceptors, and filters. Unclaimed
142
+ errors propagate to the platform for retry. Cold queue and cron events have the
143
+ same native bindings and live invalidation capabilities as HTTP.
268
144
 
269
- ### @Env()
145
+ ## WebSockets, live queries, and Durable Objects
270
146
 
271
- Parameter decorator for direct access to Cloudflare bindings in controllers. Useful as an escape hatch when you don't need a full module.
147
+ Use the native Durable Object entrypoint only in your Worker entry file:
272
148
 
273
149
  ```ts
274
- import { Env } from '@velajs/cloudflare';
275
-
276
- @Controller('/debug')
277
- class DebugController {
278
- @Get('/env')
279
- handle(@Env() env: Record<string, unknown>) {
280
- return { bindings: Object.keys(env) };
281
- }
282
-
283
- @Get('/kv')
284
- handleKV(@Env('MY_KV') kv: KVNamespace) {
285
- return kv.get('some-key');
286
- }
287
- }
288
- ```
289
-
290
- ### @Scheduled()
150
+ import { InjectionToken, Module } from '@velajs/vela';
151
+ import { LiveModule } from '@velajs/vela/live';
152
+ import {
153
+ CloudflareWebSocketModule,
154
+ createCloudflareWorker,
155
+ durableObjectCursorLog,
156
+ durableObjectLive,
157
+ } from '@velajs/cloudflare';
158
+ import { VelaWebSocketDurableObject } from '@velajs/cloudflare/durable-objects';
159
+
160
+ interface RoomEnv { ROOMS: DurableObjectNamespace<Room> }
161
+ const ROOM_ENV = new InjectionToken<RoomEnv>('room environment');
291
162
 
292
- Method decorator for cron trigger handlers.
293
-
294
- ```ts
295
- import { Scheduled } from '@velajs/cloudflare';
296
-
297
- @Injectable()
298
- class CleanupService {
299
- @Scheduled('0 * * * *') // every hour
300
- async hourlyCleanup() {
301
- console.log('Running hourly cleanup');
302
- }
303
-
304
- @Scheduled('0 0 * * *') // every day at midnight
305
- async dailyReport() {
306
- console.log('Generating daily report');
307
- }
308
- }
309
- ```
310
-
311
- ### @QueueConsumer()
312
-
313
- Method decorator for queue consumer handlers.
314
-
315
- ```ts
316
- import { QueueConsumer } from '@velajs/cloudflare';
163
+ @Module({
164
+ imports: [
165
+ CloudflareWebSocketModule.forRoot(),
166
+ LiveModule.forRootAsync({
167
+ inject: [ROOM_ENV],
168
+ useFactory: (env) => ({
169
+ driver: () => durableObjectLive({
170
+ namespace: env.ROOMS,
171
+ gatewayPath: '/rooms/:room/ws',
172
+ }),
173
+ log: () => durableObjectCursorLog(),
174
+ }),
175
+ }),
176
+ ],
177
+ // Add your @WebSocketGateway and @LiveResolver classes here.
178
+ providers: [],
179
+ })
180
+ class RoomModule {}
317
181
 
318
- @Injectable()
319
- class EmailWorker {
320
- @QueueConsumer('email-queue')
321
- async process(batch: MessageBatch) {
322
- for (const msg of batch.messages) {
323
- console.log('Sending email:', msg.body);
324
- msg.ack();
325
- }
326
- }
327
- }
182
+ export class Room extends VelaWebSocketDurableObject(RoomModule, { envToken: ROOM_ENV }) {}
183
+ export default createCloudflareWorker(RoomModule, { envToken: ROOM_ENV });
328
184
  ```
329
185
 
330
- ## createCloudflareApp
331
-
332
- Use `createCloudflareApp()` instead of `VelaFactory.create()` for Cloudflare apps. It sets up a one-time middleware that captures `c.env` on the first request and initializes all binding modules.
333
-
334
- ```ts
335
- import { createCloudflareApp } from '@velajs/cloudflare';
336
-
337
- const app = await createCloudflareApp(AppModule);
338
- export default app;
339
- ```
186
+ Declare gateways with `@WebSocketGateway({ path, roomParam, binding, ... })` and
187
+ configure origins and upgrade authentication for your application. Upgrade
188
+ routing consumes the core trusted request identity, checks conflicts with the
189
+ upgrade credential, and forwards issuer, subject, tenant, and expiry to the DO.
190
+ Client-supplied internal identity headers are stripped before authorization.
340
191
 
341
- ## Full Worker Export
192
+ Declare live query argument and result schemas once with `defineLiveQuery({ args,
193
+ result })` from `@velajs/vela/live`. Both entries accept a parser object such as a
194
+ Zod schema. Use `@LiveQuery('todos.list', definition, { tags: ['todos'] })` on the
195
+ resolver and share the definition with its client. Restored hibernation arguments
196
+ and final query results use the same validation boundary.
342
197
 
343
- To use scheduled triggers and queue consumers, export the handlers explicitly:
198
+ Each application constructs its own driver and cursor log. Workers send
199
+ invalidations through the typed DO namespace; the DO uses its own live engine
200
+ and SQLite cursor log. Configure the class in Wrangler `new_sqlite_classes` to
201
+ retain cursor/epoch state across hibernation. Hibernated subscriptions are
202
+ restored on wake; the shared live protocol handles resume or snapshot fallback.
344
203
 
345
- ```ts
346
- const app = await createCloudflareApp(AppModule);
204
+ The DO class preserves its RPC types, so `DurableObjectNamespace<Room>` exposes
205
+ `invalidate`, `broadcast`, and PITR methods without assertions. `broadcastToRoom`,
206
+ `liveInvalidateToRoom`, and the PITR helpers remain available from the root
207
+ package. `VelaNonceDurableObject` is exported from `/durable-objects`; its
208
+ `durableObjectNonceStore` factory remains on the root entrypoint.
347
209
 
348
- export default {
349
- fetch: app.fetch,
350
- scheduled: app.scheduled.bind(app),
351
- queue: app.queue.bind(app),
352
- };
353
- ```
210
+ The root package contains no runtime `cloudflare:workers` import and can be
211
+ loaded by Node tooling. Native classes belong to `/durable-objects`.
354
212
 
355
- ## Raw Binding Access
213
+ ## R2 storage and caches
356
214
 
357
- Each service exposes the underlying Cloudflare binding via a getter:
215
+ Configure named disks from an async factory using actual bucket values:
358
216
 
359
217
  ```ts
360
- const rawKV = kvService.namespace; // KVNamespace
361
- const rawD1 = d1Service.database; // D1Database
362
- const rawR2 = r2Service.bucket; // R2Bucket
363
- const rawQueue = queueService.queue; // Queue
364
- const rawDO = doService.namespace; // DurableObjectNamespace
365
- const rawAI = aiService.binding; // Ai
366
- const rawVec = vecService.index; // VectorizeIndex
367
- const rawHD = hdService.binding; // Hyperdrive
218
+ StorageModule.forRootAsync({
219
+ inject: [ENV],
220
+ useFactory: (env) => ({
221
+ defaultDisk: 'uploads',
222
+ secret: env.APP_SECRET,
223
+ disks: [{ disk: 'uploads', bucket: env.FILES, root: 'uploads/{year}' }],
224
+ presignedUrl: { defaultExpiry: 3600, maxExpiry: 86400 },
225
+ }),
226
+ });
368
227
  ```
369
228
 
370
- ## How It Works
371
-
372
- Cloudflare Workers only provide bindings (`env.DB`, `env.MY_KV`, etc.) at request time via the `env` parameter. They are stable across requests within an isolate.
373
-
374
- `createCloudflareApp` handles this by:
375
-
376
- 1. Each `XModule.forRoot()` creates a `BindingRef` (mutable holder) and registers it in the DI container
377
- 2. Services are constructed at boot time with the `BindingRef` no binding access yet
378
- 3. On the first HTTP request, a one-time middleware reads `c.env` and initializes all `BindingRef` instances
379
- 4. From that point on, services access bindings lazily through the ref
380
-
381
- This means binding-dependent services work as plain singletons with no per-request overhead.
382
-
383
- ## License
384
-
385
- MIT
229
+ `StorageService` supports upload, download, delete, existence checks, and expiring
230
+ signed download URLs. The proxy validates signatures, HTTP method, expiry, and
231
+ the configured root; returned files download as attachments.
232
+
233
+ Construct `KVCacheStore` and `KvFlagDriver` with a native namespace:
234
+ `new KVCacheStore(env.CACHE)` and `new KvFlagDriver(env.CACHE)`. Cache reads and
235
+ object-valued flag reads return `unknown`; validate them with an application
236
+ parser. Core `CacheService.getParsed(key, parser)` infers the result from that
237
+ parser. Memory and tiered cache reads use the same unknown-value contract.
238
+
239
+ ## Migration
240
+
241
+ This release intentionally changes the APIs:
242
+
243
+ - Replace `BindingRef`, `EnvRef`, `EnvModule`, `EnvService`, all per-binding
244
+ modules, and all per-binding services with a typed `InjectionToken<Env>`.
245
+ - Pass `{ env, envToken }` to `createCloudflareApp`, or export
246
+ `createCloudflareWorker(AppModule, { envToken })`.
247
+ - Use `app.get(TOKEN)` without a caller-selected generic.
248
+ - Replace storage disk `binding: 'FILES'` with `bucket: env.FILES`, and pass the
249
+ storage signing secret explicitly through module options.
250
+ - Build live `driver` and `log` through per-application factories. Pass a typed
251
+ `namespace` to `durableObjectLive` instead of a binding name.
252
+ - Import native Durable Object classes from `@velajs/cloudflare/durable-objects`
253
+ and pass `{ envToken }` to `VelaWebSocketDurableObject`.
254
+ - Cache `get<T>` was removed. Use raw `unknown` reads and a parser.
255
+ - Email and workflow integrations are outside this API package. Their deferred
256
+ standalone packages are not required to import, build, test, or install it.
257
+
258
+ Use `pnpm --dir cloudflare test`, `pnpm --dir cloudflare test:workers`, and
259
+ `pnpm --dir cloudflare typecheck` from the shared API workspace. The Workers suite
260
+ uses real KV, D1, R2, WebSockets, SQLite Durable Objects, and cold event dispatch.