@velajs/cloudflare 1.10.0 → 1.10.1

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 (109) hide show
  1. package/CHANGELOG.md +8 -3
  2. package/dist/index.d.ts +805 -41
  3. package/dist/index.js +1645 -39
  4. package/dist/index.js.map +1 -0
  5. package/package.json +54 -40
  6. package/dist/binding-ref.d.ts +0 -8
  7. package/dist/binding-ref.js +0 -19
  8. package/dist/cloudflare-application.d.ts +0 -125
  9. package/dist/cloudflare-application.js +0 -186
  10. package/dist/cloudflare-factory.d.ts +0 -88
  11. package/dist/cloudflare-factory.js +0 -107
  12. package/dist/decorators/env.d.ts +0 -16
  13. package/dist/decorators/env.js +0 -22
  14. package/dist/decorators/queue-consumer.d.ts +0 -23
  15. package/dist/decorators/queue-consumer.js +0 -40
  16. package/dist/decorators/scheduled.d.ts +0 -20
  17. package/dist/decorators/scheduled.js +0 -36
  18. package/dist/env-ref.d.ts +0 -10
  19. package/dist/env-ref.js +0 -13
  20. package/dist/modules/ai.module.d.ts +0 -1
  21. package/dist/modules/ai.module.js +0 -8
  22. package/dist/modules/create-binding-module.d.ts +0 -14
  23. package/dist/modules/create-binding-module.js +0 -42
  24. package/dist/modules/d1.module.d.ts +0 -1
  25. package/dist/modules/d1.module.js +0 -8
  26. package/dist/modules/durable-object.module.d.ts +0 -1
  27. package/dist/modules/durable-object.module.js +0 -8
  28. package/dist/modules/env.module.d.ts +0 -17
  29. package/dist/modules/env.module.js +0 -35
  30. package/dist/modules/hyperdrive.module.d.ts +0 -1
  31. package/dist/modules/hyperdrive.module.js +0 -8
  32. package/dist/modules/kv.module.d.ts +0 -1
  33. package/dist/modules/kv.module.js +0 -8
  34. package/dist/modules/queue.module.d.ts +0 -1
  35. package/dist/modules/queue.module.js +0 -8
  36. package/dist/modules/r2.module.d.ts +0 -1
  37. package/dist/modules/r2.module.js +0 -8
  38. package/dist/modules/vectorize.module.d.ts +0 -1
  39. package/dist/modules/vectorize.module.js +0 -8
  40. package/dist/services/ai.service.d.ts +0 -6
  41. package/dist/services/ai.service.js +0 -33
  42. package/dist/services/d1.service.d.ts +0 -6
  43. package/dist/services/d1.service.js +0 -33
  44. package/dist/services/durable-object.service.d.ts +0 -6
  45. package/dist/services/durable-object.service.js +0 -33
  46. package/dist/services/env.service.d.ts +0 -31
  47. package/dist/services/env.service.js +0 -36
  48. package/dist/services/flagship-flag.driver.d.ts +0 -57
  49. package/dist/services/flagship-flag.driver.js +0 -49
  50. package/dist/services/hyperdrive.service.d.ts +0 -12
  51. package/dist/services/hyperdrive.service.js +0 -51
  52. package/dist/services/kv-cache.store.d.ts +0 -19
  53. package/dist/services/kv-cache.store.js +0 -58
  54. package/dist/services/kv-flag.driver.d.ts +0 -48
  55. package/dist/services/kv-flag.driver.js +0 -62
  56. package/dist/services/kv.service.d.ts +0 -11
  57. package/dist/services/kv.service.js +0 -33
  58. package/dist/services/queue.service.d.ts +0 -6
  59. package/dist/services/queue.service.js +0 -33
  60. package/dist/services/r2.service.d.ts +0 -6
  61. package/dist/services/r2.service.js +0 -33
  62. package/dist/services/vectorize.service.d.ts +0 -6
  63. package/dist/services/vectorize.service.js +0 -33
  64. package/dist/storage/index.d.ts +0 -7
  65. package/dist/storage/index.js +0 -6
  66. package/dist/storage/r2-storage.driver.d.ts +0 -19
  67. package/dist/storage/r2-storage.driver.js +0 -61
  68. package/dist/storage/storage-manager.service.d.ts +0 -16
  69. package/dist/storage/storage-manager.service.js +0 -56
  70. package/dist/storage/storage.controller.d.ts +0 -16
  71. package/dist/storage/storage.controller.js +0 -88
  72. package/dist/storage/storage.module.d.ts +0 -7
  73. package/dist/storage/storage.module.js +0 -37
  74. package/dist/storage/storage.service.d.ts +0 -20
  75. package/dist/storage/storage.service.js +0 -78
  76. package/dist/storage/storage.tokens.d.ts +0 -3
  77. package/dist/storage/storage.tokens.js +0 -2
  78. package/dist/storage/storage.types.d.ts +0 -17
  79. package/dist/storage/storage.types.js +0 -1
  80. package/dist/tokens.d.ts +0 -12
  81. package/dist/tokens.js +0 -12
  82. package/dist/types.d.ts +0 -13
  83. package/dist/types.js +0 -1
  84. package/dist/websocket/broadcast.d.ts +0 -15
  85. package/dist/websocket/broadcast.js +0 -26
  86. package/dist/websocket/cf-room-registry.d.ts +0 -23
  87. package/dist/websocket/cf-room-registry.js +0 -65
  88. package/dist/websocket/cf-ws-client.d.ts +0 -28
  89. package/dist/websocket/cf-ws-client.js +0 -83
  90. package/dist/websocket/cloudflare-websocket.module.d.ts +0 -11
  91. package/dist/websocket/cloudflare-websocket.module.js +0 -27
  92. package/dist/websocket/do-bootstrap.d.ts +0 -24
  93. package/dist/websocket/do-bootstrap.js +0 -55
  94. package/dist/websocket/do-live.d.ts +0 -81
  95. package/dist/websocket/do-live.js +0 -197
  96. package/dist/websocket/do-state.d.ts +0 -35
  97. package/dist/websocket/do-state.js +0 -4
  98. package/dist/websocket/do-websocket-host.d.ts +0 -28
  99. package/dist/websocket/do-websocket-host.js +0 -69
  100. package/dist/websocket/index.d.ts +0 -15
  101. package/dist/websocket/index.js +0 -15
  102. package/dist/websocket/room-id.d.ts +0 -6
  103. package/dist/websocket/room-id.js +0 -12
  104. package/dist/websocket/websocket-routing.d.ts +0 -14
  105. package/dist/websocket/websocket-routing.js +0 -45
  106. package/dist/websocket/websocket.durable-object.d.ts +0 -15
  107. package/dist/websocket/websocket.durable-object.js +0 -83
  108. package/dist/websocket/ws-server-holder.d.ts +0 -16
  109. package/dist/websocket/ws-server-holder.js +0 -34
@@ -1,197 +0,0 @@
1
- import { LIVE_CURSOR_LOG, LIVE_DRIVER, readPersistedLiveSubscriptions } from "@velajs/vela/live";
2
- import { CfWsClient } from "./cf-ws-client.js";
3
- import { roomToDurableId } from "./room-id.js";
4
- const DEFAULT_ROOM = 'default';
5
- const DEFAULT_MAX_LOG_ROWS = 4096;
6
- /**
7
- * The durable `CursorLog`: an append-only tag-invalidation log in the DO's
8
- * SQLite (`__vela_live_log`, AUTOINCREMENT seq = cursor) plus an epoch UUID in
9
- * `__vela_live_meta`. Because the cursor survives hibernation AND trims (it is
10
- * read from `sqlite_sequence`, lunora's `ctx-db-cdc.ts` trick), a reconnecting
11
- * client whose gap the log still covers gets a tiny `resume` instead of a
12
- * re-run — the real-resume half of the live protocol.
13
- *
14
- * Constructed un-initialized at module-composition time (the same app module
15
- * bootstraps in the Worker AND in each DO); `initDoLive` wires the SQLite
16
- * handle inside the DO. In the Worker isolate it stays un-initialized — and is
17
- * never consulted there, because `durableObjectLive()` routes every
18
- * invalidation to the room DO's log (one log scope per room, exactly the
19
- * protocol's model).
20
- */ export class DoCursorLog {
21
- maxRows;
22
- sql;
23
- epoch;
24
- constructor(maxRows = DEFAULT_MAX_LOG_ROWS){
25
- this.maxRows = maxRows;
26
- }
27
- /** @internal — called by `initDoLive` with the DO's `ctx.storage.sql`. */ _initialize(sql) {
28
- this.sql = sql;
29
- sql.exec('CREATE TABLE IF NOT EXISTS __vela_live_log (seq INTEGER PRIMARY KEY AUTOINCREMENT, ts REAL NOT NULL, tags TEXT NOT NULL)');
30
- sql.exec('CREATE TABLE IF NOT EXISTS __vela_live_meta (k TEXT PRIMARY KEY, v TEXT NOT NULL)');
31
- const row = sql.exec("SELECT v FROM __vela_live_meta WHERE k = 'epoch'").toArray()[0];
32
- if (row && typeof row.v === 'string') {
33
- this.epoch = row.v;
34
- } else {
35
- this.epoch = crypto.randomUUID();
36
- sql.exec("INSERT INTO __vela_live_meta (k, v) VALUES ('epoch', ?)", this.epoch);
37
- }
38
- }
39
- append(tags) {
40
- const sql = this.assertReady();
41
- sql.exec('INSERT INTO __vela_live_log (ts, tags) VALUES (?, ?)', Date.now(), JSON.stringify(tags));
42
- const stamp = this.current();
43
- // Bounded retention: trimmed gaps degrade to snapshot-on-reconnect.
44
- if (stamp.cursor > this.maxRows) {
45
- sql.exec('DELETE FROM __vela_live_log WHERE seq <= ?', stamp.cursor - this.maxRows);
46
- }
47
- return stamp;
48
- }
49
- current() {
50
- const sql = this.assertReady();
51
- // sqlite_sequence survives DELETE-based trims, so the cursor never
52
- // rewinds. The table itself only materializes on the first AUTOINCREMENT
53
- // insert — before that the log is empty and the cursor is 0.
54
- let cursor = 0;
55
- try {
56
- const row = sql.exec("SELECT seq FROM sqlite_sequence WHERE name = '__vela_live_log'").toArray()[0];
57
- cursor = typeof row?.seq === 'number' ? row.seq : Number(row?.seq ?? 0);
58
- } catch {
59
- cursor = 0;
60
- }
61
- return {
62
- cursor,
63
- epoch: this.epoch
64
- };
65
- }
66
- evaluateResume(sinceCursor, sinceEpoch, subscriptionTags) {
67
- const sql = this.assertReady();
68
- const { cursor, epoch } = this.current();
69
- if (sinceEpoch !== epoch) return 'snapshot'; // forked timeline (reset/recreated DO)
70
- if (sinceCursor > cursor) return 'snapshot'; // rollback guard
71
- if (sinceCursor === cursor) return 'resume';
72
- const minRow = sql.exec('SELECT MIN(seq) AS m FROM __vela_live_log').toArray()[0];
73
- const min = minRow?.m == null ? undefined : Number(minRow.m);
74
- // The log must still cover (sinceCursor, cursor] — a trimmed gap cannot be reasoned about.
75
- if (min === undefined || min > sinceCursor + 1) return 'snapshot';
76
- const subTags = new Set(subscriptionTags);
77
- for (const row of sql.exec('SELECT tags FROM __vela_live_log WHERE seq > ?', sinceCursor).toArray()){
78
- let tags;
79
- try {
80
- tags = JSON.parse(String(row.tags));
81
- } catch {
82
- return 'snapshot';
83
- }
84
- if (Array.isArray(tags) && tags.some((tag)=>subTags.has(tag))) return 'rerun';
85
- }
86
- return 'resume';
87
- }
88
- assertReady() {
89
- if (!this.sql) {
90
- throw new Error('DoCursorLog is not initialized. It only runs inside a SQLite-backed Durable Object ' + '(wrangler: new_sqlite_classes) — Worker-side invalidations must go through durableObjectLive(), ' + "which routes them to the room DO's log.");
91
- }
92
- return this.sql;
93
- }
94
- }
95
- /**
96
- * The Cloudflare `LiveDriver`. Dual-mode, because the SAME app module
97
- * bootstraps in both isolates:
98
- *
99
- * - **Worker** (HTTP mutations, queue consumers, crons): route the command to
100
- * the room's Durable Object over the `invalidate` RPC — the same canonical
101
- * `roomToDurableId` mapping the upgrade route and `broadcastToRoom` use —
102
- * and return THAT log scope's commit stamp (what `Vela-Commit-Cursor`
103
- * must carry).
104
- * - **DO** (writes issued from inside the object): apply to the local engine.
105
- */ export function durableObjectLive(options) {
106
- let sink;
107
- let env;
108
- let localMode = false;
109
- return {
110
- kind: 'durable-object',
111
- bind (boundSink) {
112
- sink = boundSink;
113
- },
114
- _initializeEnv (capturedEnv) {
115
- env = capturedEnv;
116
- },
117
- _setLocalMode () {
118
- localMode = true;
119
- },
120
- dispatch (cmd) {
121
- if (localMode) return sink?.applyInvalidation(cmd);
122
- const namespace = env?.[options.binding];
123
- if (!namespace) {
124
- throw new Error(`durableObjectLive: binding '${options.binding}' is not available. In a Worker, ` + 'createCloudflareApp() captures env on the first request; check the wrangler binding name.');
125
- }
126
- const room = cmd.room ?? options.defaultRoom ?? DEFAULT_ROOM;
127
- const stub = namespace.get(roomToDurableId(namespace, room));
128
- return stub.invalidate({
129
- ...cmd,
130
- room
131
- });
132
- }
133
- };
134
- }
135
- const isCfLiveDriver = (value)=>typeof value === 'object' && value !== null && typeof value._setLocalMode === 'function' && typeof value._initializeEnv === 'function';
136
- /** Worker-side wiring, called from `cloudflareAdapter`'s first-request middleware. */ export function initializeWorkerLive(container, env) {
137
- let driver;
138
- try {
139
- driver = container.resolve(LIVE_DRIVER);
140
- } catch {
141
- return; // LiveModule not imported
142
- }
143
- if (isCfLiveDriver(driver)) driver._initializeEnv(env);
144
- }
145
- /**
146
- * DO-side wiring, called from `buildDoRuntime`: initialize the SQLite cursor
147
- * log, flip the driver to local mode, and replay every hibernation-persisted
148
- * subscription into the (fresh) engine so an eviction is invisible to
149
- * subscribers. Returns the engine for the `invalidate` RPC, or undefined when
150
- * the app doesn't use LiveModule.
151
- */ export function initDoLive(app, container, ctx) {
152
- const entry = app.entrypoints.ofKind('live')[0];
153
- if (!entry) return undefined;
154
- const engine = entry.meta.engine;
155
- try {
156
- const log = container.resolve(LIVE_CURSOR_LOG);
157
- if (log instanceof DoCursorLog) {
158
- const sql = ctx.storage?.sql;
159
- if (!sql) {
160
- throw new Error('DoCursorLog requires a SQLite-backed Durable Object: add this class to ' + "wrangler's `migrations[].new_sqlite_classes`. Falling back is not possible — " + 'either enable SQLite or drop the `log: durableObjectCursorLog()` option ' + '(snapshot-on-reconnect semantics).');
161
- }
162
- log._initialize(sql);
163
- }
164
- } catch (err) {
165
- // Surface misconfiguration loudly — a silently un-initialized log would
166
- // throw on the first subscribe instead.
167
- if (err instanceof Error && err.message.includes('new_sqlite_classes')) throw err;
168
- }
169
- try {
170
- const driver = container.resolve(LIVE_DRIVER);
171
- if (isCfLiveDriver(driver)) driver._setLocalMode();
172
- } catch {
173
- // LiveModule always provides LIVE_DRIVER when the engine exists; defensive only.
174
- }
175
- // Wake-time replay: subscriptions ride the hibernation attachments.
176
- for (const ws of ctx.getWebSockets()){
177
- const client = new CfWsClient(ctx, ws);
178
- for (const record of readPersistedLiveSubscriptions(client)){
179
- engine.restoreSubscription(client.path, client, record);
180
- }
181
- }
182
- return engine;
183
- }
184
- /** Ergonomic alias: the log option for `LiveModule.forRoot` on Cloudflare. */ export function durableObjectCursorLog(maxRows) {
185
- return new DoCursorLog(maxRows);
186
- }
187
- /**
188
- * Invalidate live tags in a room from a Worker (controller / cron / queue
189
- * consumer) — the live sibling of `broadcastToRoom`. Returns the room log
190
- * scope's commit stamp for `Vela-Commit-Cursor` stamping.
191
- */ export async function liveInvalidateToRoom(ns, room, tags) {
192
- const stub = ns.get(roomToDurableId(ns, room));
193
- return stub.invalidate({
194
- room,
195
- tags
196
- });
197
- }
@@ -1,35 +0,0 @@
1
- export interface WsLike {
2
- send(message: string | ArrayBuffer): void;
3
- close(code?: number, reason?: string): void;
4
- serializeAttachment(value: unknown): void;
5
- deserializeAttachment(): unknown;
6
- }
7
- /** Structural view of the DO's SQLite handle (`ctx.storage.sql`, requires `new_sqlite_classes`). */
8
- export interface SqlStorageLike {
9
- exec(query: string, ...bindings: unknown[]): {
10
- toArray(): Record<string, unknown>[];
11
- };
12
- }
13
- export interface DoStateLike {
14
- readonly id: {
15
- toString(): string;
16
- readonly name?: string | null;
17
- };
18
- acceptWebSocket(ws: WsLike, tags?: string[]): void;
19
- getWebSockets(tag?: string): WsLike[];
20
- setWebSocketAutoResponse?(pair: unknown): void;
21
- /** Present on SQLite-backed DOs — the live cursor log lives here. */
22
- readonly storage?: {
23
- sql?: SqlStorageLike;
24
- };
25
- }
26
- /** Per-connection metadata persisted in the hibernation attachment (≤ 16 KiB). */
27
- export interface WsAttachment {
28
- connId: string;
29
- userId?: string;
30
- /** The gateway route path this socket belongs to — used to route messages. */
31
- path: string;
32
- /** Dynamically-joined room names (the hub room is also a hibernation tag). */
33
- rooms: string[];
34
- data: Record<string, unknown>;
35
- }
@@ -1,4 +0,0 @@
1
- // Minimal structural views of the Durable Object runtime, so the transport
2
- // logic is unit-testable in Node with fakes. Real `DurableObjectState` and
3
- // `WebSocket` (from @cloudflare/workers-types) satisfy these structurally.
4
- /** Per-connection metadata persisted in the hibernation attachment (≤ 16 KiB). */ export { };
@@ -1,28 +0,0 @@
1
- import type { BroadcastCommand, WsDispatcher } from '@velajs/vela/websocket';
2
- import type { CfRoomRegistry } from './cf-room-registry';
3
- import type { DoStateLike, WsLike } from './do-state';
4
- /**
5
- * The socket lifecycle inside a WebSocket Durable Object, decoupled from the
6
- * `cloudflare:workers` base class so it is unit-testable with fakes. The thin
7
- * `VelaWebSocketDurableObject` shell forwards its hibernation callbacks here.
8
- */
9
- export declare class DoWebSocketHost {
10
- private readonly ctx;
11
- private readonly dispatcher;
12
- private readonly registry;
13
- private readonly gatewayPaths;
14
- constructor(ctx: DoStateLike, dispatcher: WsDispatcher, registry: CfRoomRegistry, gatewayPaths?: readonly string[]);
15
- /** The single registered gateway's path — a fallback when the Worker didn't forward `x-vela-path`. */
16
- defaultPath(): string | undefined;
17
- /**
18
- * Accept a hibernatable socket: tag it with its hub room + connection id,
19
- * persist the attachment, then fire `OnGatewayConnection` WITHOUT blocking the
20
- * 101 response (the caller returns it immediately).
21
- */
22
- accept(ws: WsLike, path: string, roomId: string, userId?: string): void;
23
- onMessage(ws: WsLike, message: string | ArrayBuffer): Promise<void>;
24
- onClose(ws: WsLike, code: number, reason: string): Promise<void>;
25
- onError(ws: WsLike, err: unknown): Promise<void>;
26
- /** Deliver a broadcast command to this DO's local sockets (RPC entry point). */
27
- broadcast(cmd: BroadcastCommand): void;
28
- }
@@ -1,69 +0,0 @@
1
- import { CfWsClient } from "./cf-ws-client.js";
2
- import { connTag, roomTag } from "./room-id.js";
3
- /**
4
- * The socket lifecycle inside a WebSocket Durable Object, decoupled from the
5
- * `cloudflare:workers` base class so it is unit-testable with fakes. The thin
6
- * `VelaWebSocketDurableObject` shell forwards its hibernation callbacks here.
7
- */ export class DoWebSocketHost {
8
- ctx;
9
- dispatcher;
10
- registry;
11
- gatewayPaths;
12
- constructor(ctx, dispatcher, registry, gatewayPaths = []){
13
- this.ctx = ctx;
14
- this.dispatcher = dispatcher;
15
- this.registry = registry;
16
- this.gatewayPaths = gatewayPaths;
17
- }
18
- /** The single registered gateway's path — a fallback when the Worker didn't forward `x-vela-path`. */ defaultPath() {
19
- return this.gatewayPaths[0];
20
- }
21
- /**
22
- * Accept a hibernatable socket: tag it with its hub room + connection id,
23
- * persist the attachment, then fire `OnGatewayConnection` WITHOUT blocking the
24
- * 101 response (the caller returns it immediately).
25
- */ accept(ws, path, roomId, userId) {
26
- const connId = crypto.randomUUID();
27
- this.ctx.acceptWebSocket(ws, [
28
- roomTag(roomId),
29
- connTag(connId)
30
- ]);
31
- const attachment = {
32
- connId,
33
- userId,
34
- path,
35
- rooms: [
36
- roomId
37
- ],
38
- data: userId ? {
39
- userId
40
- } : {}
41
- };
42
- ws.serializeAttachment(attachment);
43
- void Promise.resolve(this.dispatcher.handleOpen(path, new CfWsClient(this.ctx, ws))).catch((err)=>console.warn('[vela] websocket handleConnection failed:', err));
44
- }
45
- async onMessage(ws, message) {
46
- const client = new CfWsClient(this.ctx, ws);
47
- await this.dispatcher.dispatchMessage(client.path, client, message);
48
- }
49
- async onClose(ws, code, reason) {
50
- const client = new CfWsClient(this.ctx, ws);
51
- await this.dispatcher.handleClose(client.path, client, code, reason);
52
- // Complete the closing handshake. Only 1000 and 3000-4999 are valid for
53
- // close(); reserved/abnormal codes (1005/1006/1015, etc.) throw a RangeError,
54
- // so fall back to a codeless close on those.
55
- try {
56
- if (code === 1000 || code >= 3000 && code <= 4999) ws.close(code, reason);
57
- else ws.close();
58
- } catch {
59
- // socket already closed
60
- }
61
- }
62
- async onError(ws, err) {
63
- const client = new CfWsClient(this.ctx, ws);
64
- await this.dispatcher.handleError(client.path, client, err);
65
- }
66
- /** Deliver a broadcast command to this DO's local sockets (RPC entry point). */ broadcast(cmd) {
67
- this.registry.deliverLocal(cmd);
68
- }
69
- }
@@ -1,15 +0,0 @@
1
- export { VelaWebSocketDurableObject } from './websocket.durable-object';
2
- export { CloudflareWebSocketModule } from './cloudflare-websocket.module';
3
- export { broadcastToRoom } from './broadcast';
4
- export { DoCursorLog, durableObjectCursorLog, durableObjectLive, initDoLive, initializeWorkerLive, liveInvalidateToRoom, } from './do-live';
5
- export type { CfLiveDriver, DurableObjectLiveOptions } from './do-live';
6
- export { CfWsClient } from './cf-ws-client';
7
- export { CfRoomRegistry } from './cf-room-registry';
8
- export { DoWebSocketHost } from './do-websocket-host';
9
- export { WsServerHolder } from './ws-server-holder';
10
- export { buildDoRuntime } from './do-bootstrap';
11
- export type { DoRuntime } from './do-bootstrap';
12
- export { registerWebSocketRoutes, collectWsGatewayRoutes } from './websocket-routing';
13
- export type { WsGatewayRoute } from './websocket-routing';
14
- export { roomTag, connTag, roomToDurableId } from './room-id';
15
- export type { DoStateLike, SqlStorageLike, WsLike, WsAttachment } from './do-state';
@@ -1,15 +0,0 @@
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
- // Live queries: durable cursor log + DO-routed invalidation driver
7
- export { DoCursorLog, durableObjectCursorLog, durableObjectLive, initDoLive, initializeWorkerLive, liveInvalidateToRoom } from "./do-live.js";
8
- // Transport internals (advanced use / testing)
9
- export { CfWsClient } from "./cf-ws-client.js";
10
- export { CfRoomRegistry } from "./cf-room-registry.js";
11
- export { DoWebSocketHost } from "./do-websocket-host.js";
12
- export { WsServerHolder } from "./ws-server-holder.js";
13
- export { buildDoRuntime } from "./do-bootstrap.js";
14
- export { registerWebSocketRoutes, collectWsGatewayRoutes } from "./websocket-routing.js";
15
- export { roomTag, connTag, roomToDurableId } from "./room-id.js";
@@ -1,6 +0,0 @@
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;
@@ -1,12 +0,0 @@
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
- }
@@ -1,14 +0,0 @@
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;
@@ -1,45 +0,0 @@
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
- }
@@ -1,15 +0,0 @@
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>>;
@@ -1,83 +0,0 @@
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
- liveEngine;
21
- ready;
22
- constructor(ctx, env){
23
- super(ctx, env);
24
- // Application-level ping/pong answered WITHOUT waking a hibernated DO.
25
- try {
26
- ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(PING, PONG));
27
- } catch {
28
- // Older runtimes without auto-response — fine, protocol pings still work.
29
- }
30
- this.ready = ctx.blockConcurrencyWhile(async ()=>{
31
- const runtime = await buildDoRuntime(rootModule, ctx, env);
32
- this.host = new DoWebSocketHost(ctx, runtime.dispatcher, runtime.registry, runtime.gatewayPaths);
33
- this.liveEngine = runtime.live;
34
- });
35
- }
36
- async fetch(request) {
37
- await this.ready;
38
- if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket') {
39
- return new Response('Expected WebSocket upgrade', {
40
- status: 426
41
- });
42
- }
43
- const { 0: client, 1: server } = new WebSocketPair();
44
- const url = new URL(request.url);
45
- // Headers are primary (carry auth + multi-gateway routing); fall back to the
46
- // DO's own name (set via idFromName(room)) and the single gateway path so a
47
- // plain `stub.fetch(request)` forward still works.
48
- const roomId = request.headers.get('x-vela-room') ?? this.ctx.id.name ?? url.pathname;
49
- const path = request.headers.get('x-vela-path') ?? this.host.defaultPath() ?? url.pathname;
50
- const userId = request.headers.get('x-vela-user') || undefined;
51
- this.host.accept(server, path, roomId, userId);
52
- return new Response(null, {
53
- status: 101,
54
- webSocket: client
55
- });
56
- }
57
- async webSocketMessage(ws, message) {
58
- await this.ready;
59
- await this.host.onMessage(ws, message);
60
- }
61
- async webSocketClose(ws, code, reason) {
62
- await this.ready;
63
- await this.host.onClose(ws, code, reason);
64
- }
65
- async webSocketError(ws, error) {
66
- await this.ready;
67
- await this.host.onError(ws, error);
68
- }
69
- /** DO RPC — server-initiated broadcast forwarded from a Worker (see `broadcastToRoom`). */ async broadcast(cmd) {
70
- await this.ready;
71
- this.host.broadcast(cmd);
72
- }
73
- /**
74
- * DO RPC — live tag invalidation forwarded from a Worker (durableObjectLive /
75
- * liveInvalidateToRoom). Appends to THIS log scope's cursor log and returns
76
- * the commit stamp (what `Vela-Commit-Cursor` carries); the subscription
77
- * refreshes fan out asynchronously.
78
- */ async invalidate(cmd) {
79
- await this.ready;
80
- return this.liveEngine?.applyInvalidation(cmd);
81
- }
82
- };
83
- }
@@ -1,16 +0,0 @@
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
- }
@@ -1,34 +0,0 @@
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);