@velajs/cloudflare 1.3.0 → 1.6.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.
- package/CHANGELOG.md +12 -0
- package/README.md +8 -8
- package/dist/cloudflare-application.d.ts +23 -6
- package/dist/cloudflare-application.js +26 -6
- package/dist/cloudflare-factory.js +13 -8
- package/dist/env-ref.d.ts +10 -0
- package/dist/env-ref.js +13 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/modules/create-binding-module.js +19 -22
- package/dist/modules/env.module.d.ts +17 -0
- package/dist/modules/env.module.js +35 -0
- package/dist/services/env.service.d.ts +31 -0
- package/dist/services/env.service.js +36 -0
- package/dist/services/kv-cache.store.d.ts +19 -0
- package/dist/services/kv-cache.store.js +58 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/r2-storage.driver.d.ts +19 -0
- package/dist/storage/r2-storage.driver.js +61 -0
- package/dist/storage/storage-manager.service.d.ts +16 -0
- package/dist/storage/storage-manager.service.js +56 -0
- package/dist/storage/storage.controller.d.ts +16 -0
- package/dist/storage/storage.controller.js +88 -0
- package/dist/storage/storage.module.d.ts +7 -0
- package/dist/storage/storage.module.js +37 -0
- package/dist/storage/storage.service.d.ts +20 -0
- package/dist/storage/storage.service.js +78 -0
- package/dist/storage/storage.tokens.d.ts +3 -0
- package/dist/storage/storage.tokens.js +2 -0
- package/dist/storage/storage.types.d.ts +17 -0
- package/dist/storage/storage.types.js +1 -0
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.js +2 -0
- package/dist/websocket/broadcast.d.ts +15 -0
- package/dist/websocket/broadcast.js +26 -0
- package/dist/websocket/cf-room-registry.d.ts +23 -0
- package/dist/websocket/cf-room-registry.js +65 -0
- package/dist/websocket/cf-ws-client.d.ts +28 -0
- package/dist/websocket/cf-ws-client.js +83 -0
- package/dist/websocket/cloudflare-websocket.module.d.ts +11 -0
- package/dist/websocket/cloudflare-websocket.module.js +27 -0
- package/dist/websocket/do-bootstrap.d.ts +19 -0
- package/dist/websocket/do-bootstrap.js +43 -0
- package/dist/websocket/do-state.d.ts +25 -0
- package/dist/websocket/do-state.js +4 -0
- package/dist/websocket/do-websocket-host.d.ts +27 -0
- package/dist/websocket/do-websocket-host.js +67 -0
- package/dist/websocket/index.d.ts +13 -0
- package/dist/websocket/index.js +13 -0
- package/dist/websocket/room-id.d.ts +6 -0
- package/dist/websocket/room-id.js +12 -0
- package/dist/websocket/websocket-routing.d.ts +14 -0
- package/dist/websocket/websocket-routing.js +45 -0
- package/dist/websocket/websocket.durable-object.d.ts +15 -0
- package/dist/websocket/websocket.durable-object.js +72 -0
- package/dist/websocket/ws-server-holder.d.ts +16 -0
- package/dist/websocket/ws-server-holder.js +34 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.0 (2026-07-01)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Multi-disk `StorageModule` over R2** (`StorageService.put/get/delete/exists/url`, per-disk templated roots, bucket-by-name via `EnvService`, `R2StorageDriver`) + a signature-gated `StorageController` presign-proxy.
|
|
8
|
+
- **`KVCacheStore`** implementing vela's `AsyncCacheStore` — pair with `TieredCacheStore` for a memory→KV cache.
|
|
9
|
+
- WebSocket transport for vela's WebSocket gateways (Durable Object backed).
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Multiple same-type bindings** (e.g. two `KVModule.forRoot` with different bindings) now all initialize — `collectBindingRefs` enumerates every binding ref across module buckets instead of resolving each token once.
|
|
14
|
+
|
|
3
15
|
## 0.2.0 (2026-04-28)
|
|
4
16
|
|
|
5
17
|
### Breaking changes
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ class HyperdriveService { readonly binding: Hyperdrive; }
|
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
36
|
import { Controller, Get, Module, Injectable, Param } from '@velajs/vela';
|
|
37
|
-
import {
|
|
37
|
+
import { createCloudflareApp, KVModule, KVService, D1Module, D1Service } from '@velajs/cloudflare';
|
|
38
38
|
|
|
39
39
|
@Injectable()
|
|
40
40
|
class UserService {
|
|
@@ -73,7 +73,7 @@ class UserController {
|
|
|
73
73
|
})
|
|
74
74
|
class AppModule {}
|
|
75
75
|
|
|
76
|
-
export default await
|
|
76
|
+
export default await createCloudflareApp(AppModule);
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
The `binding` string matches the binding name in your `wrangler.toml`:
|
|
@@ -327,14 +327,14 @@ class EmailWorker {
|
|
|
327
327
|
}
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
-
##
|
|
330
|
+
## createCloudflareApp
|
|
331
331
|
|
|
332
|
-
Use `
|
|
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
333
|
|
|
334
334
|
```ts
|
|
335
|
-
import {
|
|
335
|
+
import { createCloudflareApp } from '@velajs/cloudflare';
|
|
336
336
|
|
|
337
|
-
const app = await
|
|
337
|
+
const app = await createCloudflareApp(AppModule);
|
|
338
338
|
export default app;
|
|
339
339
|
```
|
|
340
340
|
|
|
@@ -343,7 +343,7 @@ export default app;
|
|
|
343
343
|
To use scheduled triggers and queue consumers, export the handlers explicitly:
|
|
344
344
|
|
|
345
345
|
```ts
|
|
346
|
-
const app = await
|
|
346
|
+
const app = await createCloudflareApp(AppModule);
|
|
347
347
|
|
|
348
348
|
export default {
|
|
349
349
|
fetch: app.fetch,
|
|
@@ -371,7 +371,7 @@ const rawHD = hdService.binding; // Hyperdrive
|
|
|
371
371
|
|
|
372
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
373
|
|
|
374
|
-
`
|
|
374
|
+
`createCloudflareApp` handles this by:
|
|
375
375
|
|
|
376
376
|
1. Each `XModule.forRoot()` creates a `BindingRef` (mutable holder) and registers it in the DI container
|
|
377
377
|
2. Services are constructed at boot time with the `BindingRef` — no binding access yet
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import { type VelaApplication } from '@velajs/vela';
|
|
3
|
+
import { type WsGatewayRoute } from './websocket/websocket-routing';
|
|
3
4
|
import type { CloudflareEnv } from './types';
|
|
4
5
|
/**
|
|
5
6
|
* Options accepted by {@link CloudflareApplication.mountOpenApi}.
|
|
@@ -34,22 +35,36 @@ export type MountOpenApiOptions = Parameters<VelaApplication['mountOpenApi']>[0]
|
|
|
34
35
|
* const app = await createCloudflareApp(AppModule);
|
|
35
36
|
* const document = createOpenApiDocument(AppModule);
|
|
36
37
|
* app.mountOpenApi({ document, ui: 'scalar' });
|
|
37
|
-
* // GET /
|
|
38
|
-
* // GET /
|
|
38
|
+
* // GET /openapi.json -> JSON document
|
|
39
|
+
* // GET /scalar -> Scalar UI (loads from CDN)
|
|
39
40
|
* ```
|
|
40
41
|
*/
|
|
41
42
|
export declare class CloudflareApplication {
|
|
42
43
|
private app;
|
|
43
44
|
private scheduledHandlers;
|
|
44
45
|
private queueConsumers;
|
|
46
|
+
private wsGatewayRoutes;
|
|
45
47
|
constructor(app: VelaApplication);
|
|
46
48
|
get fetch(): Hono['fetch'];
|
|
47
49
|
getHonoApp(): Hono;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a provider from the application's DI container (delegates to
|
|
52
|
+
* `VelaApplication.get`). Handy for grabbing a service — e.g. an auth service —
|
|
53
|
+
* to use inside `createCloudflareApp({ middleware: [...] })` request middleware,
|
|
54
|
+
* which runs outside the DI request pipeline.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const app = await createCloudflareApp(AppModule);
|
|
59
|
+
* const auth = app.get<BetterAuthService>(BetterAuthService);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
get<T>(token: Parameters<VelaApplication['get']>[0]): T;
|
|
48
63
|
/**
|
|
49
64
|
* Serve a pre-built OpenAPI document (and optionally a Scalar UI) on the
|
|
50
65
|
* underlying Hono app. Delegates verbatim to `VelaApplication.mountOpenApi`,
|
|
51
|
-
* so the JSON endpoint defaults to `/
|
|
52
|
-
* opted in) defaults to `/
|
|
66
|
+
* so the JSON endpoint defaults to `/openapi.json` and the Scalar UI (when
|
|
67
|
+
* opted in) defaults to `/scalar`. Edge-safe — the UI HTML loads Scalar from
|
|
53
68
|
* a CDN at runtime, nothing is bundled server-side.
|
|
54
69
|
*
|
|
55
70
|
* @example
|
|
@@ -61,13 +76,15 @@ export declare class CloudflareApplication {
|
|
|
61
76
|
* info: { title: 'My API', version: '1.0.0' },
|
|
62
77
|
* });
|
|
63
78
|
* app.mountOpenApi({ document, ui: 'scalar' });
|
|
64
|
-
* // GET /
|
|
65
|
-
* // GET /
|
|
79
|
+
* // GET /openapi.json -> { openapi: '3.1.0', ... }
|
|
80
|
+
* // GET /scalar -> Scalar UI HTML
|
|
66
81
|
* ```
|
|
67
82
|
*/
|
|
68
83
|
mountOpenApi(options: MountOpenApiOptions): this;
|
|
69
84
|
/** @internal — scans instances for @Scheduled, @Cron, and @QueueConsumer metadata */
|
|
70
85
|
scanInstances(instances: unknown[]): void;
|
|
86
|
+
/** @internal — upgrade routes discovered from `@WebSocketGateway({ path, binding })`. */
|
|
87
|
+
getWsGatewayRoutes(): WsGatewayRoute[];
|
|
71
88
|
/**
|
|
72
89
|
* Handle Cloudflare scheduled (cron) events.
|
|
73
90
|
* Matches the event's cron expression to `@Scheduled()` and vela `@Cron()` handlers.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CRON_METADATA, getMetadata } from "@velajs/vela";
|
|
2
2
|
import { getScheduledMetadata } from "./decorators/scheduled.js";
|
|
3
3
|
import { getQueueConsumerMetadata } from "./decorators/queue-consumer.js";
|
|
4
|
+
import { collectWsGatewayRoutes } from "./websocket/websocket-routing.js";
|
|
4
5
|
function invoke(instance, methodName, args) {
|
|
5
6
|
const method = instance[methodName];
|
|
6
7
|
if (typeof method !== 'function') {
|
|
@@ -33,13 +34,14 @@ function invoke(instance, methodName, args) {
|
|
|
33
34
|
* const app = await createCloudflareApp(AppModule);
|
|
34
35
|
* const document = createOpenApiDocument(AppModule);
|
|
35
36
|
* app.mountOpenApi({ document, ui: 'scalar' });
|
|
36
|
-
* // GET /
|
|
37
|
-
* // GET /
|
|
37
|
+
* // GET /openapi.json -> JSON document
|
|
38
|
+
* // GET /scalar -> Scalar UI (loads from CDN)
|
|
38
39
|
* ```
|
|
39
40
|
*/ export class CloudflareApplication {
|
|
40
41
|
app;
|
|
41
42
|
scheduledHandlers = [];
|
|
42
43
|
queueConsumers = [];
|
|
44
|
+
wsGatewayRoutes = [];
|
|
43
45
|
constructor(app){
|
|
44
46
|
this.app = app;
|
|
45
47
|
}
|
|
@@ -50,10 +52,24 @@ function invoke(instance, methodName, args) {
|
|
|
50
52
|
return this.app.getHonoApp();
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
55
|
+
* Resolve a provider from the application's DI container (delegates to
|
|
56
|
+
* `VelaApplication.get`). Handy for grabbing a service — e.g. an auth service —
|
|
57
|
+
* to use inside `createCloudflareApp({ middleware: [...] })` request middleware,
|
|
58
|
+
* which runs outside the DI request pipeline.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const app = await createCloudflareApp(AppModule);
|
|
63
|
+
* const auth = app.get<BetterAuthService>(BetterAuthService);
|
|
64
|
+
* ```
|
|
65
|
+
*/ get(token) {
|
|
66
|
+
return this.app.get(token);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
53
69
|
* Serve a pre-built OpenAPI document (and optionally a Scalar UI) on the
|
|
54
70
|
* underlying Hono app. Delegates verbatim to `VelaApplication.mountOpenApi`,
|
|
55
|
-
* so the JSON endpoint defaults to `/
|
|
56
|
-
* opted in) defaults to `/
|
|
71
|
+
* so the JSON endpoint defaults to `/openapi.json` and the Scalar UI (when
|
|
72
|
+
* opted in) defaults to `/scalar`. Edge-safe — the UI HTML loads Scalar from
|
|
57
73
|
* a CDN at runtime, nothing is bundled server-side.
|
|
58
74
|
*
|
|
59
75
|
* @example
|
|
@@ -65,8 +81,8 @@ function invoke(instance, methodName, args) {
|
|
|
65
81
|
* info: { title: 'My API', version: '1.0.0' },
|
|
66
82
|
* });
|
|
67
83
|
* app.mountOpenApi({ document, ui: 'scalar' });
|
|
68
|
-
* // GET /
|
|
69
|
-
* // GET /
|
|
84
|
+
* // GET /openapi.json -> { openapi: '3.1.0', ... }
|
|
85
|
+
* // GET /scalar -> Scalar UI HTML
|
|
70
86
|
* ```
|
|
71
87
|
*/ mountOpenApi(options) {
|
|
72
88
|
this.app.mountOpenApi(options);
|
|
@@ -98,8 +114,12 @@ function invoke(instance, methodName, args) {
|
|
|
98
114
|
queueName: meta.queueName
|
|
99
115
|
});
|
|
100
116
|
}
|
|
117
|
+
this.wsGatewayRoutes.push(...collectWsGatewayRoutes(instance));
|
|
101
118
|
}
|
|
102
119
|
}
|
|
120
|
+
/** @internal — upgrade routes discovered from `@WebSocketGateway({ path, binding })`. */ getWsGatewayRoutes() {
|
|
121
|
+
return this.wsGatewayRoutes;
|
|
122
|
+
}
|
|
103
123
|
/**
|
|
104
124
|
* Handle Cloudflare scheduled (cron) events.
|
|
105
125
|
* Matches the event's cron expression to `@Scheduled()` and vela `@Cron()` handlers.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { VelaFactory } from "@velajs/vela";
|
|
2
2
|
import { BindingRef } from "./binding-ref.js";
|
|
3
3
|
import { CloudflareApplication } from "./cloudflare-application.js";
|
|
4
|
+
import { EnvRef } from "./env-ref.js";
|
|
5
|
+
import { registerWebSocketRoutes } from "./websocket/websocket-routing.js";
|
|
4
6
|
// Walks every provider registered in the container and pulls out the
|
|
5
7
|
// BindingRef instances. Replaces the old module-level `bindingsRegistry`
|
|
6
8
|
// global so two CloudflareApplication instances in one process don't
|
|
7
9
|
// share state.
|
|
8
10
|
function collectBindingRefs(container) {
|
|
11
|
+
// Enumerate per-instance useValue providers across ALL module buckets. Two
|
|
12
|
+
// same-type binding modules (e.g. KVModule.forRoot for CACHE and SESSIONS)
|
|
13
|
+
// share one token but live in distinct buckets — resolving the token would
|
|
14
|
+
// return only the first, leaving the others uninitialized.
|
|
9
15
|
const refs = [];
|
|
10
|
-
for (const
|
|
11
|
-
let value;
|
|
12
|
-
try {
|
|
13
|
-
value = container.resolve(token);
|
|
14
|
-
} catch {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
16
|
+
for (const value of container.getUseValues()){
|
|
17
17
|
if (value instanceof BindingRef) refs.push(value);
|
|
18
18
|
}
|
|
19
19
|
return refs;
|
|
@@ -56,7 +56,9 @@ function collectBindingRefs(container) {
|
|
|
56
56
|
initialized = true;
|
|
57
57
|
const env = c.env ?? {};
|
|
58
58
|
for (const ref of refs){
|
|
59
|
-
|
|
59
|
+
// EnvRef holds the whole env; every other ref holds one binding.
|
|
60
|
+
if (ref instanceof EnvRef) ref._initialize(env);
|
|
61
|
+
else ref._initialize(env[ref.bindingName]);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
await next();
|
|
@@ -73,5 +75,8 @@ function collectBindingRefs(container) {
|
|
|
73
75
|
refs = collectBindingRefs(velaApp.getContainer());
|
|
74
76
|
const cfApp = new CloudflareApplication(velaApp);
|
|
75
77
|
cfApp.scanInstances(velaApp.getInstances());
|
|
78
|
+
// Register WebSocket upgrade routes for any @WebSocketGateway({ path, binding }).
|
|
79
|
+
// Each forwards the upgrade to the room's Durable Object (which owns the socket).
|
|
80
|
+
registerWebSocketRoutes(cfApp.getHonoApp(), cfApp.getWsGatewayRoutes());
|
|
76
81
|
return cfApp;
|
|
77
82
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BindingRef } from './binding-ref';
|
|
2
|
+
/**
|
|
3
|
+
* Holds the entire Cloudflare Worker `env` record (bindings + vars/secrets),
|
|
4
|
+
* not a single binding. Subclasses {@link BindingRef} so createCloudflareApp's
|
|
5
|
+
* binding-init middleware collects and initializes it through the same path;
|
|
6
|
+
* the factory special-cases it to pass the full `env` instead of one binding.
|
|
7
|
+
*/
|
|
8
|
+
export declare class EnvRef extends BindingRef<Record<string, unknown>> {
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
package/dist/env-ref.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BindingRef } from "./binding-ref.js";
|
|
2
|
+
// Sentinel binding name for the whole-env holder — never used as an env key.
|
|
3
|
+
const ENV_SENTINEL = '__cf_env__';
|
|
4
|
+
/**
|
|
5
|
+
* Holds the entire Cloudflare Worker `env` record (bindings + vars/secrets),
|
|
6
|
+
* not a single binding. Subclasses {@link BindingRef} so createCloudflareApp's
|
|
7
|
+
* binding-init middleware collects and initializes it through the same path;
|
|
8
|
+
* the factory special-cases it to pass the full `env` instead of one binding.
|
|
9
|
+
*/ export class EnvRef extends BindingRef {
|
|
10
|
+
constructor(){
|
|
11
|
+
super(ENV_SENTINEL);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,11 @@ export { DurableObjectModule } from './modules/durable-object.module';
|
|
|
10
10
|
export { AIModule } from './modules/ai.module';
|
|
11
11
|
export { VectorizeModule } from './modules/vectorize.module';
|
|
12
12
|
export { HyperdriveModule } from './modules/hyperdrive.module';
|
|
13
|
+
export { EnvModule } from './modules/env.module';
|
|
14
|
+
export { StorageModule, StorageService, StorageManagerService, StorageController, R2StorageDriver, STORAGE_OPTIONS, } from './storage/index';
|
|
15
|
+
export type { StorageModuleOptions, DiskConfig, PresignedUrlConfig } from './storage/index';
|
|
13
16
|
export { KVService } from './services/kv.service';
|
|
17
|
+
export { KVCacheStore } from './services/kv-cache.store';
|
|
14
18
|
export { D1Service } from './services/d1.service';
|
|
15
19
|
export { R2Service } from './services/r2.service';
|
|
16
20
|
export { QueueService } from './services/queue.service';
|
|
@@ -18,9 +22,14 @@ export { DurableObjectService } from './services/durable-object.service';
|
|
|
18
22
|
export { AIService } from './services/ai.service';
|
|
19
23
|
export { VectorizeService } from './services/vectorize.service';
|
|
20
24
|
export { HyperdriveService } from './services/hyperdrive.service';
|
|
25
|
+
export { EnvService } from './services/env.service';
|
|
21
26
|
export { Env } from './decorators/env';
|
|
22
27
|
export { Scheduled } from './decorators/scheduled';
|
|
23
28
|
export { QueueConsumer } from './decorators/queue-consumer';
|
|
29
|
+
export { VelaWebSocketDurableObject, CloudflareWebSocketModule, broadcastToRoom, } from './websocket/index';
|
|
30
|
+
export type { WsGatewayRoute } from './websocket/index';
|
|
31
|
+
export { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket, WebSocketServer, WsException, } from '@velajs/vela/websocket';
|
|
32
|
+
export type { WsClient, WsServer, WsResponse, WsMessage, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, } from '@velajs/vela/websocket';
|
|
24
33
|
export type { CloudflareEnv, ScheduledRegistration, QueueRegistration } from './types';
|
|
25
34
|
export type { ScheduledMetadata } from './decorators/scheduled';
|
|
26
35
|
export type { QueueConsumerMetadata } from './decorators/queue-consumer';
|
package/dist/index.js
CHANGED
|
@@ -10,8 +10,12 @@ export { DurableObjectModule } from "./modules/durable-object.module.js";
|
|
|
10
10
|
export { AIModule } from "./modules/ai.module.js";
|
|
11
11
|
export { VectorizeModule } from "./modules/vectorize.module.js";
|
|
12
12
|
export { HyperdriveModule } from "./modules/hyperdrive.module.js";
|
|
13
|
+
export { EnvModule } from "./modules/env.module.js";
|
|
14
|
+
// Storage (multi-disk over R2 + presign proxy)
|
|
15
|
+
export { StorageModule, StorageService, StorageManagerService, StorageController, R2StorageDriver, STORAGE_OPTIONS } from "./storage/index.js";
|
|
13
16
|
// Services
|
|
14
17
|
export { KVService } from "./services/kv.service.js";
|
|
18
|
+
export { KVCacheStore } from "./services/kv-cache.store.js";
|
|
15
19
|
export { D1Service } from "./services/d1.service.js";
|
|
16
20
|
export { R2Service } from "./services/r2.service.js";
|
|
17
21
|
export { QueueService } from "./services/queue.service.js";
|
|
@@ -19,7 +23,12 @@ export { DurableObjectService } from "./services/durable-object.service.js";
|
|
|
19
23
|
export { AIService } from "./services/ai.service.js";
|
|
20
24
|
export { VectorizeService } from "./services/vectorize.service.js";
|
|
21
25
|
export { HyperdriveService } from "./services/hyperdrive.service.js";
|
|
26
|
+
export { EnvService } from "./services/env.service.js";
|
|
22
27
|
// Decorators
|
|
23
28
|
export { Env } from "./decorators/env.js";
|
|
24
29
|
export { Scheduled } from "./decorators/scheduled.js";
|
|
25
30
|
export { QueueConsumer } from "./decorators/queue-consumer.js";
|
|
31
|
+
// WebSocket (Durable Object transport for the Vela WebSocketModule)
|
|
32
|
+
export { VelaWebSocketDurableObject, CloudflareWebSocketModule, broadcastToRoom } from "./websocket/index.js";
|
|
33
|
+
// Re-export the core gateway API so a Cloudflare app can import it from one place.
|
|
34
|
+
export { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket, WebSocketServer, WsException } from "@velajs/vela/websocket";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { defineConfigurableModule } from "@velajs/vela";
|
|
1
2
|
import { BindingRef } from "../binding-ref.js";
|
|
2
3
|
// Single factory for every Cloudflare binding module (KV, D1, R2, ...).
|
|
3
|
-
// Each binding module is just a bag of (token, service); this generates
|
|
4
|
-
//
|
|
4
|
+
// Each binding module is just a bag of (token, service); this generates the
|
|
5
|
+
// canonical forRoot() shape via vela's `defineConfigurableModule` engine.
|
|
5
6
|
//
|
|
6
7
|
// Identity model (post vela audit #2):
|
|
7
8
|
// - One real module class is declared per binding TYPE (KV, D1, R2, ...).
|
|
@@ -22,24 +23,20 @@ export function createBindingModule(opts) {
|
|
|
22
23
|
[className]: class {
|
|
23
24
|
}
|
|
24
25
|
}[className];
|
|
25
|
-
return {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
]
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
};
|
|
26
|
+
return defineConfigurableModule({
|
|
27
|
+
module: moduleClass,
|
|
28
|
+
methodName: 'forRoot',
|
|
29
|
+
keyFrom: ({ binding })=>binding,
|
|
30
|
+
providers: ({ binding })=>[
|
|
31
|
+
{
|
|
32
|
+
provide: opts.bindingRefToken,
|
|
33
|
+
useValue: new BindingRef(binding)
|
|
34
|
+
},
|
|
35
|
+
opts.serviceClass
|
|
36
|
+
],
|
|
37
|
+
exports: [
|
|
38
|
+
opts.serviceClass,
|
|
39
|
+
opts.bindingRefToken
|
|
40
|
+
]
|
|
41
|
+
});
|
|
45
42
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DynamicModule } from '@velajs/vela';
|
|
2
|
+
/**
|
|
3
|
+
* Provides {@link EnvService} GLOBALLY so any module's provider factories can
|
|
4
|
+
* `inject: [EnvService]`. Register once on the root module:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* @Module({ imports: [EnvModule.forRoot(), AuthModule.forRootAsync({ ... })] })
|
|
8
|
+
* class AppModule {}
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* The {@link EnvRef} it provides is collected and initialized by
|
|
12
|
+
* createCloudflareApp's binding-init middleware on the first request (the
|
|
13
|
+
* factory passes it the full `env`, not a single binding).
|
|
14
|
+
*/
|
|
15
|
+
export declare class EnvModule {
|
|
16
|
+
static forRoot(): DynamicModule;
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { EnvRef } from "../env-ref.js";
|
|
2
|
+
import { EnvService } from "../services/env.service.js";
|
|
3
|
+
import { ENV_REF } from "../tokens.js";
|
|
4
|
+
/**
|
|
5
|
+
* Provides {@link EnvService} GLOBALLY so any module's provider factories can
|
|
6
|
+
* `inject: [EnvService]`. Register once on the root module:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* @Module({ imports: [EnvModule.forRoot(), AuthModule.forRootAsync({ ... })] })
|
|
10
|
+
* class AppModule {}
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* The {@link EnvRef} it provides is collected and initialized by
|
|
14
|
+
* createCloudflareApp's binding-init middleware on the first request (the
|
|
15
|
+
* factory passes it the full `env`, not a single binding).
|
|
16
|
+
*/ export class EnvModule {
|
|
17
|
+
static forRoot() {
|
|
18
|
+
const ref = new EnvRef();
|
|
19
|
+
return {
|
|
20
|
+
module: EnvModule,
|
|
21
|
+
global: true,
|
|
22
|
+
providers: [
|
|
23
|
+
{
|
|
24
|
+
provide: ENV_REF,
|
|
25
|
+
useValue: ref
|
|
26
|
+
},
|
|
27
|
+
EnvService
|
|
28
|
+
],
|
|
29
|
+
exports: [
|
|
30
|
+
EnvService,
|
|
31
|
+
ENV_REF
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { EnvRef } from '../env-ref';
|
|
2
|
+
/**
|
|
3
|
+
* Injectable access to the full Cloudflare Worker `env` (bindings + vars +
|
|
4
|
+
* secrets).
|
|
5
|
+
*
|
|
6
|
+
* The per-binding services (`D1Service`, `KVService`, …) each expose a single
|
|
7
|
+
* binding, and the `@Env()` param decorator only works inside a request-scoped
|
|
8
|
+
* controller handler. `EnvService` fills the gap: it can be injected into
|
|
9
|
+
* PROVIDER FACTORIES — e.g. `SomeModule.forRootAsync({ inject: [EnvService] })`
|
|
10
|
+
* — so a factory can read secrets/origins without reaching for the request.
|
|
11
|
+
*
|
|
12
|
+
* Reads are lazy. `env` only exists per request, so a factory (which runs at
|
|
13
|
+
* bootstrap) must capture the `EnvService` and read inside its callback:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* AuthModule.forRootAsync({
|
|
17
|
+
* inject: [EnvService],
|
|
18
|
+
* useFactory: (env: EnvService) => buildAuth(() => env.get<string>('AUTH_SECRET')),
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Reading eagerly at bootstrap (`env.get(...)` before any request) throws.
|
|
23
|
+
*/
|
|
24
|
+
export declare class EnvService {
|
|
25
|
+
private ref;
|
|
26
|
+
constructor(ref: EnvRef);
|
|
27
|
+
/** The full env record. Throws if read before the first request. */
|
|
28
|
+
get env(): Record<string, unknown>;
|
|
29
|
+
/** Read a single env entry (binding, var, or secret) by name. */
|
|
30
|
+
get<T = unknown>(key: string): T | undefined;
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
function _ts_metadata(k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
}
|
|
10
|
+
function _ts_param(paramIndex, decorator) {
|
|
11
|
+
return function(target, key) {
|
|
12
|
+
decorator(target, key, paramIndex);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
import { Inject, Injectable } from "@velajs/vela";
|
|
16
|
+
import { ENV_REF } from "../tokens.js";
|
|
17
|
+
export class EnvService {
|
|
18
|
+
ref;
|
|
19
|
+
constructor(ref){
|
|
20
|
+
this.ref = ref;
|
|
21
|
+
}
|
|
22
|
+
/** The full env record. Throws if read before the first request. */ get env() {
|
|
23
|
+
return this.ref.value;
|
|
24
|
+
}
|
|
25
|
+
/** Read a single env entry (binding, var, or secret) by name. */ get(key) {
|
|
26
|
+
return this.ref.value[key];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
EnvService = _ts_decorate([
|
|
30
|
+
Injectable(),
|
|
31
|
+
_ts_param(0, Inject(ENV_REF)),
|
|
32
|
+
_ts_metadata("design:type", Function),
|
|
33
|
+
_ts_metadata("design:paramtypes", [
|
|
34
|
+
typeof EnvRef === "undefined" ? Object : EnvRef
|
|
35
|
+
])
|
|
36
|
+
], EnvService);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AsyncCacheStore } from '@velajs/vela';
|
|
2
|
+
import { KVService } from './kv.service';
|
|
3
|
+
/**
|
|
4
|
+
* Cloudflare KV-backed {@link CacheStore}. Values are JSON-encoded. Intended as
|
|
5
|
+
* the slow tier under a `TieredCacheStore` (memory L1 → KV L2), but usable
|
|
6
|
+
* standalone as `CacheModule.forRootAsync({ inject: [KVService], useFactory: (kv) => ({ store: new KVCacheStore(kv) }) })`.
|
|
7
|
+
*
|
|
8
|
+
* Note: Cloudflare KV requires `expirationTtl >= 60s`, so sub-minute TTLs are
|
|
9
|
+
* clamped up. Keep short TTLs on the memory tier; use KV for longer-lived entries.
|
|
10
|
+
*/
|
|
11
|
+
export declare class KVCacheStore implements AsyncCacheStore {
|
|
12
|
+
private readonly kv;
|
|
13
|
+
constructor(kv: KVService);
|
|
14
|
+
private get ns();
|
|
15
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
16
|
+
set<T = unknown>(key: string, value: T, ttl?: number): Promise<void>;
|
|
17
|
+
del(key: string): Promise<void>;
|
|
18
|
+
clear(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
function _ts_metadata(k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
}
|
|
10
|
+
function _ts_param(paramIndex, decorator) {
|
|
11
|
+
return function(target, key) {
|
|
12
|
+
decorator(target, key, paramIndex);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
import { Inject, Injectable } from "@velajs/vela";
|
|
16
|
+
import { KVService } from "./kv.service.js";
|
|
17
|
+
export class KVCacheStore {
|
|
18
|
+
kv;
|
|
19
|
+
constructor(kv){
|
|
20
|
+
this.kv = kv;
|
|
21
|
+
}
|
|
22
|
+
get ns() {
|
|
23
|
+
return this.kv.namespace;
|
|
24
|
+
}
|
|
25
|
+
async get(key) {
|
|
26
|
+
const value = await this.ns.get(key, 'json');
|
|
27
|
+
return value === null ? undefined : value;
|
|
28
|
+
}
|
|
29
|
+
async set(key, value, ttl) {
|
|
30
|
+
// KV enforces a 60s minimum expirationTtl; clamp up. Omit for no-TTL.
|
|
31
|
+
const options = ttl !== undefined ? {
|
|
32
|
+
expirationTtl: Math.max(60, Math.floor(ttl))
|
|
33
|
+
} : undefined;
|
|
34
|
+
await this.ns.put(key, JSON.stringify(value), options);
|
|
35
|
+
}
|
|
36
|
+
async del(key) {
|
|
37
|
+
await this.ns.delete(key);
|
|
38
|
+
}
|
|
39
|
+
async clear() {
|
|
40
|
+
// KV has no native clear — page through and delete. Best-effort.
|
|
41
|
+
let cursor;
|
|
42
|
+
do {
|
|
43
|
+
const list = await this.ns.list(cursor ? {
|
|
44
|
+
cursor
|
|
45
|
+
} : undefined);
|
|
46
|
+
await Promise.all(list.keys.map((entry)=>this.ns.delete(entry.name)));
|
|
47
|
+
cursor = list.list_complete ? undefined : list.cursor;
|
|
48
|
+
}while (cursor)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
KVCacheStore = _ts_decorate([
|
|
52
|
+
Injectable(),
|
|
53
|
+
_ts_param(0, Inject(KVService)),
|
|
54
|
+
_ts_metadata("design:type", Function),
|
|
55
|
+
_ts_metadata("design:paramtypes", [
|
|
56
|
+
typeof KVService === "undefined" ? Object : KVService
|
|
57
|
+
])
|
|
58
|
+
], KVCacheStore);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { StorageModule } from './storage.module';
|
|
2
|
+
export { StorageService } from './storage.service';
|
|
3
|
+
export { StorageManagerService } from './storage-manager.service';
|
|
4
|
+
export { StorageController } from './storage.controller';
|
|
5
|
+
export { R2StorageDriver } from './r2-storage.driver';
|
|
6
|
+
export { STORAGE_OPTIONS } from './storage.tokens';
|
|
7
|
+
export type { StorageModuleOptions, DiskConfig, PresignedUrlConfig } from './storage.types';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { StorageModule } from "./storage.module.js";
|
|
2
|
+
export { StorageService } from "./storage.service.js";
|
|
3
|
+
export { StorageManagerService } from "./storage-manager.service.js";
|
|
4
|
+
export { StorageController } from "./storage.controller.js";
|
|
5
|
+
export { R2StorageDriver } from "./r2-storage.driver.js";
|
|
6
|
+
export { STORAGE_OPTIONS } from "./storage.tokens.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DownloadResult, type PresignedUrlResult, type PresignMethod, type StorageBody, type StorageDriver, type UploadOptions, type UploadResult } from '@velajs/vela/storage';
|
|
2
|
+
/** Base path of the StorageController presign-proxy route. */
|
|
3
|
+
export declare const STORAGE_ROUTE_BASE = "/storage";
|
|
4
|
+
export interface R2StorageDriverConfig {
|
|
5
|
+
disk: string;
|
|
6
|
+
bucket: R2Bucket;
|
|
7
|
+
/** HMAC secret for presigned URLs (typically env.APP_SECRET). */
|
|
8
|
+
secret?: string;
|
|
9
|
+
}
|
|
10
|
+
/** {@link StorageDriver} over a Cloudflare R2 bucket. */
|
|
11
|
+
export declare class R2StorageDriver implements StorageDriver {
|
|
12
|
+
private readonly config;
|
|
13
|
+
constructor(config: R2StorageDriverConfig);
|
|
14
|
+
upload(body: StorageBody, path: string, options: UploadOptions): Promise<UploadResult>;
|
|
15
|
+
download(path: string): Promise<DownloadResult>;
|
|
16
|
+
delete(path: string): Promise<void>;
|
|
17
|
+
exists(path: string): Promise<boolean>;
|
|
18
|
+
getPresignedUrl(path: string, method: PresignMethod, expiresIn: number): Promise<PresignedUrlResult>;
|
|
19
|
+
}
|