@velajs/cloudflare 1.5.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +8 -8
  3. package/dist/cloudflare-application.d.ts +4 -0
  4. package/dist/cloudflare-application.js +6 -0
  5. package/dist/cloudflare-factory.js +9 -7
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.js +7 -0
  8. package/dist/modules/create-binding-module.js +19 -22
  9. package/dist/services/kv-cache.store.d.ts +19 -0
  10. package/dist/services/kv-cache.store.js +58 -0
  11. package/dist/storage/index.d.ts +7 -0
  12. package/dist/storage/index.js +6 -0
  13. package/dist/storage/r2-storage.driver.d.ts +19 -0
  14. package/dist/storage/r2-storage.driver.js +61 -0
  15. package/dist/storage/storage-manager.service.d.ts +16 -0
  16. package/dist/storage/storage-manager.service.js +56 -0
  17. package/dist/storage/storage.controller.d.ts +16 -0
  18. package/dist/storage/storage.controller.js +88 -0
  19. package/dist/storage/storage.module.d.ts +7 -0
  20. package/dist/storage/storage.module.js +37 -0
  21. package/dist/storage/storage.service.d.ts +20 -0
  22. package/dist/storage/storage.service.js +78 -0
  23. package/dist/storage/storage.tokens.d.ts +3 -0
  24. package/dist/storage/storage.tokens.js +2 -0
  25. package/dist/storage/storage.types.d.ts +17 -0
  26. package/dist/storage/storage.types.js +1 -0
  27. package/dist/websocket/broadcast.d.ts +15 -0
  28. package/dist/websocket/broadcast.js +26 -0
  29. package/dist/websocket/cf-room-registry.d.ts +23 -0
  30. package/dist/websocket/cf-room-registry.js +65 -0
  31. package/dist/websocket/cf-ws-client.d.ts +28 -0
  32. package/dist/websocket/cf-ws-client.js +83 -0
  33. package/dist/websocket/cloudflare-websocket.module.d.ts +11 -0
  34. package/dist/websocket/cloudflare-websocket.module.js +27 -0
  35. package/dist/websocket/do-bootstrap.d.ts +19 -0
  36. package/dist/websocket/do-bootstrap.js +43 -0
  37. package/dist/websocket/do-state.d.ts +25 -0
  38. package/dist/websocket/do-state.js +4 -0
  39. package/dist/websocket/do-websocket-host.d.ts +27 -0
  40. package/dist/websocket/do-websocket-host.js +67 -0
  41. package/dist/websocket/index.d.ts +13 -0
  42. package/dist/websocket/index.js +13 -0
  43. package/dist/websocket/room-id.d.ts +6 -0
  44. package/dist/websocket/room-id.js +12 -0
  45. package/dist/websocket/websocket-routing.d.ts +14 -0
  46. package/dist/websocket/websocket-routing.js +45 -0
  47. package/dist/websocket/websocket.durable-object.d.ts +15 -0
  48. package/dist/websocket/websocket.durable-object.js +72 -0
  49. package/dist/websocket/ws-server-holder.d.ts +16 -0
  50. package/dist/websocket/ws-server-holder.js +34 -0
  51. package/package.json +8 -17
@@ -0,0 +1,13 @@
1
+ // DO base class (imports `cloudflare:workers` — only runs in workerd).
2
+ export { VelaWebSocketDurableObject } from "./websocket.durable-object.js";
3
+ // Module + server-initiated emit helper
4
+ export { CloudflareWebSocketModule } from "./cloudflare-websocket.module.js";
5
+ export { broadcastToRoom } from "./broadcast.js";
6
+ // Transport internals (advanced use / testing)
7
+ export { CfWsClient } from "./cf-ws-client.js";
8
+ export { CfRoomRegistry } from "./cf-room-registry.js";
9
+ export { DoWebSocketHost } from "./do-websocket-host.js";
10
+ export { WsServerHolder } from "./ws-server-holder.js";
11
+ export { buildDoRuntime } from "./do-bootstrap.js";
12
+ export { registerWebSocketRoutes, collectWsGatewayRoutes } from "./websocket-routing.js";
13
+ export { roomTag, connTag, roomToDurableId } from "./room-id.js";
@@ -0,0 +1,6 @@
1
+ /** Hibernation tag marking a socket's hub room (set at accept; immutable after). */
2
+ export declare function roomTag(roomId: string): string;
3
+ /** Hibernation tag addressing one connection directly. */
4
+ export declare function connTag(connId: string): string;
5
+ /** One Durable Object instance per room, addressed by name. */
6
+ export declare function roomToDurableId(ns: DurableObjectNamespace, roomId: string): DurableObjectId;
@@ -0,0 +1,12 @@
1
+ // Canonical room ↔ Durable Object mappings. The SAME functions are used by the
2
+ // Worker upgrade route (to pick the DO) and by every server-initiated emit — so
3
+ // a connection and a later broadcast always resolve to the same DO instance.
4
+ /** Hibernation tag marking a socket's hub room (set at accept; immutable after). */ export function roomTag(roomId) {
5
+ return `room:${roomId}`;
6
+ }
7
+ /** Hibernation tag addressing one connection directly. */ export function connTag(connId) {
8
+ return `conn:${connId}`;
9
+ }
10
+ /** One Durable Object instance per room, addressed by name. */ export function roomToDurableId(ns, roomId) {
11
+ return ns.idFromName(roomId);
12
+ }
@@ -0,0 +1,14 @@
1
+ import type { Hono } from 'hono';
2
+ export interface WsGatewayRoute {
3
+ path: string;
4
+ binding: string;
5
+ }
6
+ /** Read `@WebSocketGateway({ path, binding })` off a resolved instance (CF-hosted gateways only). */
7
+ export declare function collectWsGatewayRoutes(instance: object): WsGatewayRoute[];
8
+ /**
9
+ * Registers the upgrade routes on the Worker's Hono app. Each route validates
10
+ * the `Upgrade` header, resolves the room's Durable Object, and forwards the raw
11
+ * request — injecting spoof-safe `x-vela-*` headers the DO reads. The DO returns
12
+ * the `101` with the client socket.
13
+ */
14
+ export declare function registerWebSocketRoutes(hono: Hono, routes: WsGatewayRoute[]): void;
@@ -0,0 +1,45 @@
1
+ import { getMetadata } from "@velajs/vela";
2
+ import { WS_GATEWAY_METADATA } from "@velajs/vela/websocket";
3
+ import { roomToDurableId } from "./room-id.js";
4
+ /** Read `@WebSocketGateway({ path, binding })` off a resolved instance (CF-hosted gateways only). */ export function collectWsGatewayRoutes(instance) {
5
+ const options = getMetadata(WS_GATEWAY_METADATA, instance.constructor);
6
+ if (!options?.path || !options?.binding) return [];
7
+ return [
8
+ {
9
+ path: options.path,
10
+ binding: options.binding
11
+ }
12
+ ];
13
+ }
14
+ /**
15
+ * Registers the upgrade routes on the Worker's Hono app. Each route validates
16
+ * the `Upgrade` header, resolves the room's Durable Object, and forwards the raw
17
+ * request — injecting spoof-safe `x-vela-*` headers the DO reads. The DO returns
18
+ * the `101` with the client socket.
19
+ */ export function registerWebSocketRoutes(hono, routes) {
20
+ for (const route of routes){
21
+ hono.get(route.path, async (c)=>{
22
+ if (c.req.header('upgrade')?.toLowerCase() !== 'websocket') {
23
+ return c.text('Expected WebSocket upgrade', 426);
24
+ }
25
+ const ns = c.env[route.binding];
26
+ if (!ns) {
27
+ return c.text(`Durable Object binding '${route.binding}' is not configured`, 500);
28
+ }
29
+ const roomId = c.req.param('id') ?? route.path;
30
+ const stub = ns.get(roomToDurableId(ns, roomId));
31
+ // Strip any client-supplied x-vela-* (anti-spoof), then set server values.
32
+ const headers = new Headers(c.req.raw.headers);
33
+ headers.delete('x-vela-room');
34
+ headers.delete('x-vela-path');
35
+ headers.delete('x-vela-user');
36
+ headers.set('x-vela-room', roomId);
37
+ headers.set('x-vela-path', route.path);
38
+ const userId = c.get('userId');
39
+ if (userId) headers.set('x-vela-user', String(userId));
40
+ return stub.fetch(new Request(c.req.raw, {
41
+ headers
42
+ }));
43
+ });
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ import { DurableObject } from 'cloudflare:workers';
2
+ import type { Type } from '@velajs/vela';
3
+ /**
4
+ * Base class for the WebSocket Durable Object. The user exports a named subclass
5
+ * (matching their `wrangler.toml` `class_name`) built from their `AppModule`:
6
+ *
7
+ * ```ts
8
+ * export class ChatRoom extends VelaWebSocketDurableObject(AppModule) {}
9
+ * ```
10
+ *
11
+ * It owns the raw hibernation socket lifecycle (Hono's `upgradeWebSocket` cannot
12
+ * bridge DO hibernation) and forwards every event into the runtime-agnostic
13
+ * `WsDispatcher` via {@link DoWebSocketHost}.
14
+ */
15
+ export declare function VelaWebSocketDurableObject(rootModule: Type): new (ctx: DurableObjectState, env: Record<string, unknown>) => DurableObject<Record<string, unknown>>;
@@ -0,0 +1,72 @@
1
+ import { DurableObject } from "cloudflare:workers";
2
+ import { buildDoRuntime } from "./do-bootstrap.js";
3
+ import { DoWebSocketHost } from "./do-websocket-host.js";
4
+ const PING = '{"event":"ping"}';
5
+ const PONG = '{"event":"pong"}';
6
+ /**
7
+ * Base class for the WebSocket Durable Object. The user exports a named subclass
8
+ * (matching their `wrangler.toml` `class_name`) built from their `AppModule`:
9
+ *
10
+ * ```ts
11
+ * export class ChatRoom extends VelaWebSocketDurableObject(AppModule) {}
12
+ * ```
13
+ *
14
+ * It owns the raw hibernation socket lifecycle (Hono's `upgradeWebSocket` cannot
15
+ * bridge DO hibernation) and forwards every event into the runtime-agnostic
16
+ * `WsDispatcher` via {@link DoWebSocketHost}.
17
+ */ export function VelaWebSocketDurableObject(rootModule) {
18
+ return class VelaWsDurableObject extends DurableObject {
19
+ host;
20
+ ready;
21
+ constructor(ctx, env){
22
+ super(ctx, env);
23
+ // Application-level ping/pong answered WITHOUT waking a hibernated DO.
24
+ try {
25
+ ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(PING, PONG));
26
+ } catch {
27
+ // Older runtimes without auto-response — fine, protocol pings still work.
28
+ }
29
+ this.ready = ctx.blockConcurrencyWhile(async ()=>{
30
+ const runtime = await buildDoRuntime(rootModule, ctx, env);
31
+ this.host = new DoWebSocketHost(ctx, runtime.dispatcher, runtime.registry);
32
+ });
33
+ }
34
+ async fetch(request) {
35
+ await this.ready;
36
+ if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket') {
37
+ return new Response('Expected WebSocket upgrade', {
38
+ status: 426
39
+ });
40
+ }
41
+ const { 0: client, 1: server } = new WebSocketPair();
42
+ const url = new URL(request.url);
43
+ // Headers are primary (carry auth + multi-gateway routing); fall back to the
44
+ // DO's own name (set via idFromName(room)) and the single gateway path so a
45
+ // plain `stub.fetch(request)` forward still works.
46
+ const roomId = request.headers.get('x-vela-room') ?? this.ctx.id.name ?? url.pathname;
47
+ const path = request.headers.get('x-vela-path') ?? this.host.defaultPath() ?? url.pathname;
48
+ const userId = request.headers.get('x-vela-user') || undefined;
49
+ this.host.accept(server, path, roomId, userId);
50
+ return new Response(null, {
51
+ status: 101,
52
+ webSocket: client
53
+ });
54
+ }
55
+ async webSocketMessage(ws, message) {
56
+ await this.ready;
57
+ await this.host.onMessage(ws, message);
58
+ }
59
+ async webSocketClose(ws, code, reason) {
60
+ await this.ready;
61
+ await this.host.onClose(ws, code, reason);
62
+ }
63
+ async webSocketError(ws, error) {
64
+ await this.ready;
65
+ await this.host.onError(ws, error);
66
+ }
67
+ /** DO RPC — server-initiated broadcast forwarded from a Worker (see `broadcastToRoom`). */ async broadcast(cmd) {
68
+ await this.ready;
69
+ this.host.broadcast(cmd);
70
+ }
71
+ };
72
+ }
@@ -0,0 +1,16 @@
1
+ import type { BroadcastOperator, WsServer } from '@velajs/vela/websocket';
2
+ /**
3
+ * Late-bound `WsServer`. Provided as `WS_SERVER` (one per DI container, i.e. per
4
+ * Durable Object instance), then pointed at the ctx-backed server once the DO
5
+ * builds. Gateways inject it via `@WebSocketServer()`; it throws if used before
6
+ * a runtime binds it (e.g. from the stateless Worker isolate).
7
+ */
8
+ export declare class WsServerHolder implements WsServer {
9
+ private target?;
10
+ setTarget(server: WsServer): void;
11
+ private get resolved();
12
+ emit(event: string, data?: unknown): void | Promise<void>;
13
+ to(room: string): BroadcastOperator;
14
+ in(room: string): BroadcastOperator;
15
+ except(room: string): BroadcastOperator;
16
+ }
@@ -0,0 +1,34 @@
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
+ import { Injectable } from "@velajs/vela";
8
+ export class WsServerHolder {
9
+ target;
10
+ setTarget(server) {
11
+ this.target = server;
12
+ }
13
+ get resolved() {
14
+ if (!this.target) {
15
+ throw new Error('WebSocket server is only available inside a WebSocket Durable Object. To ' + 'push from a Worker HTTP handler, use broadcastToRoom(namespace, room, ...).');
16
+ }
17
+ return this.target;
18
+ }
19
+ emit(event, data) {
20
+ return this.resolved.emit(event, data);
21
+ }
22
+ to(room) {
23
+ return this.resolved.to(room);
24
+ }
25
+ in(room) {
26
+ return this.resolved.in(room);
27
+ }
28
+ except(room) {
29
+ return this.resolved.except(room);
30
+ }
31
+ }
32
+ WsServerHolder = _ts_decorate([
33
+ Injectable()
34
+ ], WsServerHolder);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/cloudflare",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Cloudflare Workers integration for Vela framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,13 +17,6 @@
17
17
  "LICENSE",
18
18
  "CHANGELOG.md"
19
19
  ],
20
- "scripts": {
21
- "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
22
- "test": "vitest run",
23
- "typecheck": "tsc --noEmit",
24
- "prepare": "swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
25
- "prepublishOnly": "pnpm run typecheck && pnpm test"
26
- },
27
20
  "sideEffects": false,
28
21
  "keywords": [
29
22
  "vela",
@@ -52,24 +45,22 @@
52
45
  },
53
46
  "peerDependencies": {
54
47
  "@cloudflare/workers-types": ">=4",
55
- "@velajs/vela": ">=1.8.5",
48
+ "@velajs/vela": ">=1.10.0",
56
49
  "hono": ">=4"
57
50
  },
58
51
  "devDependencies": {
59
52
  "@cloudflare/workers-types": "^4.20260624.1",
60
53
  "@swc/cli": "^0.8.1",
61
54
  "@swc/core": "^1.15.43",
62
- "@velajs/vela": "^1.8.5",
55
+ "@velajs/vela": "^1.10.0",
63
56
  "hono": "^4.12.27",
64
57
  "typescript": "^6.0.3",
65
58
  "unplugin-swc": "^1.5.9",
66
59
  "vitest": "^4.1.9"
67
60
  },
68
- "packageManager": "pnpm@10.20.0",
69
- "pnpm": {
70
- "onlyBuiltDependencies": [
71
- "@swc/core",
72
- "esbuild"
73
- ]
61
+ "scripts": {
62
+ "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
63
+ "test": "vitest run",
64
+ "typecheck": "tsc --noEmit"
74
65
  }
75
- }
66
+ }