@velajs/cloudflare 1.22.1 → 1.23.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 +51 -0
- package/README.md +39 -22
- package/dist/durable-objects.d.ts +1 -1
- package/dist/durable-objects.js +40 -21
- package/dist/durable-objects.js.map +1 -1
- package/dist/index.d.ts +22 -9
- package/dist/index.js +107 -59
- package/dist/index.js.map +1 -1
- package/dist/{nonce-validation-LE0vFk-7.js → nonce-validation-Bcqf3FvY.js} +106 -38
- package/dist/nonce-validation-Bcqf3FvY.js.map +1 -0
- package/dist/queue.d.ts +26 -0
- package/dist/queue.js +41 -0
- package/dist/queue.js.map +1 -0
- package/package.json +10 -6
- package/dist/nonce-validation-LE0vFk-7.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.23.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a95951a: Add explicit Unix/Cloudflare cron dialects, UTC selection and validated schedule metadata for deployment introspection. Fix Sunday-ending ranges and numeric coercion, reject invalid timer delays, and provide native scheduled handler types while preserving exact Workers trigger matching and Node local-time defaults.
|
|
8
|
+
- 6b7cf23: Add Standard Schema job definitions with inferred producer input and validated processor output. Preserve original wire input across transport, await all processor outcomes, and offer opt-in strict unmatched routing. Add awaited Cloudflare producer and per-message consumer bridge helpers that validate envelopes, use native attempts, and preserve explicit ack/retry semantics.
|
|
9
|
+
|
|
10
|
+
Preserve processor module ownership through discovery and dispatch, resolve scoped components asynchronously, and finish managed invocation work before settling delivery.
|
|
11
|
+
- 5205e58: Validate WebSocket correlation envelopes and hibernation attachments, preserve live baselines after refused sends, and add bounded connection-local send admission and incoming work. Existing void send APIs and unversioned 1.x attachments remain supported.
|
|
12
|
+
|
|
13
|
+
Drop frames still waiting on Node connection setup after overload or close. Use browser-valid private close codes and reconnect after client-side send admission failures.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 26fe8bf: Resolve queue and scheduled handlers and their pipeline components asynchronously
|
|
18
|
+
in the owning module's child scope. Seed execution context ownership, defer
|
|
19
|
+
handler construction until guards pass, track native waitUntil work through
|
|
20
|
+
provider disposal, and await every matching handler before returning failures.
|
|
21
|
+
|
|
22
|
+
Read validated WebSocket entrypoint metadata for upgrade routes so scoped gateways
|
|
23
|
+
do not require a bootstrap instance; retain legacy forwarding metadata scanning.
|
|
24
|
+
- Updated dependencies [c6a43a6]
|
|
25
|
+
- Updated dependencies [bbe62d4]
|
|
26
|
+
- Updated dependencies [a6ef933]
|
|
27
|
+
- Updated dependencies [dae3654]
|
|
28
|
+
- Updated dependencies [77cca9e]
|
|
29
|
+
- Updated dependencies [b9f75f5]
|
|
30
|
+
- Updated dependencies [df47ea8]
|
|
31
|
+
- Updated dependencies [af019bf]
|
|
32
|
+
- Updated dependencies [6df1059]
|
|
33
|
+
- Updated dependencies [bdd90a1]
|
|
34
|
+
- Updated dependencies [8a3923f]
|
|
35
|
+
- Updated dependencies [c7d108b]
|
|
36
|
+
- Updated dependencies [1c7f635]
|
|
37
|
+
- Updated dependencies [636ffbc]
|
|
38
|
+
- Updated dependencies [54f8864]
|
|
39
|
+
- Updated dependencies [f49db45]
|
|
40
|
+
- Updated dependencies [4fde903]
|
|
41
|
+
- Updated dependencies [6a1b5b3]
|
|
42
|
+
- Updated dependencies [a95951a]
|
|
43
|
+
- Updated dependencies [9e82187]
|
|
44
|
+
- Updated dependencies [c5a3cb0]
|
|
45
|
+
- Updated dependencies [363fb71]
|
|
46
|
+
- Updated dependencies [de4e57e]
|
|
47
|
+
- Updated dependencies [0765aaa]
|
|
48
|
+
- Updated dependencies [6b7cf23]
|
|
49
|
+
- Updated dependencies [5205e58]
|
|
50
|
+
- Updated dependencies [ae45689]
|
|
51
|
+
- @velajs/vela@1.25.0
|
|
52
|
+
- @velajs/feature-flags@1.22.1
|
|
53
|
+
|
|
3
54
|
## 1.22.1
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -85,6 +85,27 @@ environment.
|
|
|
85
85
|
`cloudflareAdapter({ env, envToken })` provides the same bootstrap and request
|
|
86
86
|
contract when composing `VelaFactory.create` directly.
|
|
87
87
|
|
|
88
|
+
## Managed queue and cron work
|
|
89
|
+
|
|
90
|
+
Each matching queue/cron handler receives its original event and environment,
|
|
91
|
+
plus a context whose `waitUntil(promise)` delegates to the platform and retains
|
|
92
|
+
that handler's DI scope until the promise settles. Class/method guards,
|
|
93
|
+
interceptors and filters resolve asynchronously from the handler's declaring
|
|
94
|
+
module. The execution context exposes that same child via `getContainer()` and
|
|
95
|
+
its owner via `getModuleId()`; `REQUEST_CONTEXT` remains HTTP-only.
|
|
96
|
+
|
|
97
|
+
Inject `EXECUTION_LIFETIME` from `@velajs/vela` to schedule deferred callbacks
|
|
98
|
+
with `lifetime.defer(work)` or register already-started work with
|
|
99
|
+
`lifetime.waitUntil(promise)`. The handler, managed work and asynchronous provider
|
|
100
|
+
disposal finish before queue/cron dispatch returns. Unclaimed failures reject
|
|
101
|
+
for the platform to observe; they are not silently converted into success. When
|
|
102
|
+
several handlers match, every handler settles before a single failure or
|
|
103
|
+
`AggregateError` is returned. Background failure does not undo completed writes;
|
|
104
|
+
handlers still need the idempotency appropriate to their delivery semantics.
|
|
105
|
+
|
|
106
|
+
See [execution scopes](../../docs/execution-scopes.md) for lifetime ownership,
|
|
107
|
+
stream boundaries, cancellation and optional transport integration.
|
|
108
|
+
|
|
88
109
|
## Typed provider factories
|
|
89
110
|
|
|
90
111
|
Bindings retain their full native API and generic parameters. There are no
|
|
@@ -212,6 +233,11 @@ loaded by Node tooling. Native classes belong to `/durable-objects`.
|
|
|
212
233
|
|
|
213
234
|
## R2 storage and caches
|
|
214
235
|
|
|
236
|
+
For new object/file storage, prefer the independently imported
|
|
237
|
+
[`@velajs/storage`](../storage/README.md#portable-storage-and-the-cloudflare-proxy)
|
|
238
|
+
with a native R2 or hybrid driver. The storage module below remains the supported
|
|
239
|
+
1.x Worker HMAC proxy API; its signed routes differ from provider-signed URLs.
|
|
240
|
+
|
|
215
241
|
Configure named disks from an async factory using actual bucket values:
|
|
216
242
|
|
|
217
243
|
```ts
|
|
@@ -236,25 +262,16 @@ object-valued flag reads return `unknown`; validate them with an application
|
|
|
236
262
|
parser. Core `CacheService.getParsed(key, parser)` infers the result from that
|
|
237
263
|
parser. Memory and tiered cache reads use the same unknown-value contract.
|
|
238
264
|
|
|
239
|
-
##
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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.
|
|
265
|
+
## Development
|
|
266
|
+
|
|
267
|
+
From the repository root:
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
pnpm --filter @velajs/cloudflare test
|
|
271
|
+
pnpm --filter @velajs/cloudflare test:workers
|
|
272
|
+
pnpm --filter @velajs/cloudflare typecheck
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The Workers suite uses real KV, D1, R2, WebSockets, SQLite Durable Objects, and
|
|
276
|
+
cold event dispatch. See the [security guide](https://github.com/velajs/vela/blob/main/docs/cloudflare-security.md) for trusted identity,
|
|
277
|
+
URL signing, and WebSocket boundaries.
|
|
@@ -9,7 +9,7 @@ import { DurableObject } from "cloudflare:workers";
|
|
|
9
9
|
* (matching their `wrangler.toml` `class_name`) built from their `AppModule`:
|
|
10
10
|
*
|
|
11
11
|
* ```ts
|
|
12
|
-
* export class ChatRoom extends VelaWebSocketDurableObject(AppModule) {}
|
|
12
|
+
* export class ChatRoom extends VelaWebSocketDurableObject(AppModule, { envToken: ENV }) {}
|
|
13
13
|
* ```
|
|
14
14
|
*
|
|
15
15
|
* It owns the raw hibernation socket lifecycle (Hono's `upgradeWebSocket` cannot
|
package/dist/durable-objects.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as
|
|
1
|
+
import { S as connTag, _ as readDoPitrBookmark, d as rejectedAttachment, f as socketAttachment, h as armDoPitr, l as CfWsClient, n as isValidExpiry, o as initDoLive, p as WsServerHolder, s as initializeDoLiveResources, t as isCanonicalBoundedText, w as roomTag, x as registerCloudflareEnvironment, y as resolveCloudflareRoot } from "./nonce-validation-Bcqf3FvY.js";
|
|
2
2
|
import { VelaApplication, bootstrap } from "@velajs/vela";
|
|
3
|
-
import { WS_SERVER, WsDispatcher, WsServerImpl, assertBroadcastCommandFits, local, readWsEntrypointMeta } from "@velajs/vela/websocket";
|
|
3
|
+
import { WS_SERVER, WebSocketSendGate, WsDispatcher, WsMessageQueue, WsServerImpl, assertBroadcastCommandFits, local, readWsEntrypointMeta } from "@velajs/vela/websocket";
|
|
4
4
|
import { DurableObject } from "cloudflare:workers";
|
|
5
5
|
//#region src/websocket/cf-room-registry.ts
|
|
6
6
|
/**
|
|
@@ -11,6 +11,11 @@ import { DurableObject } from "cloudflare:workers";
|
|
|
11
11
|
*/
|
|
12
12
|
var CfRoomRegistry = class {
|
|
13
13
|
ctx;
|
|
14
|
+
#sendGates = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
#sendPolicyForPath;
|
|
16
|
+
setSendPolicyResolver(resolve) {
|
|
17
|
+
this.#sendPolicyForPath = resolve;
|
|
18
|
+
}
|
|
14
19
|
deliveryAuthorizer;
|
|
15
20
|
frameLimitForPath;
|
|
16
21
|
constructor(ctx) {
|
|
@@ -42,8 +47,6 @@ var CfRoomRegistry = class {
|
|
|
42
47
|
const selected = [];
|
|
43
48
|
for (const ws of targets) {
|
|
44
49
|
const att = this.reconcileFrameLimit(ws, this.attachmentOf(ws));
|
|
45
|
-
if (seen.has(att.connId)) continue;
|
|
46
|
-
seen.add(att.connId);
|
|
47
50
|
if (att.state !== "active" || att.expiresAtMs !== void 0 && (!Number.isSafeInteger(att.expiresAtMs) || att.expiresAtMs <= Date.now())) {
|
|
48
51
|
try {
|
|
49
52
|
att.state = "rejected";
|
|
@@ -52,11 +55,13 @@ var CfRoomRegistry = class {
|
|
|
52
55
|
} catch {}
|
|
53
56
|
continue;
|
|
54
57
|
}
|
|
58
|
+
if (seen.has(att.connId)) continue;
|
|
59
|
+
seen.add(att.connId);
|
|
55
60
|
if (excludeIds.has(att.connId)) continue;
|
|
56
61
|
if (excludeRooms.some((r) => att.rooms.includes(r))) continue;
|
|
57
62
|
selected.push({
|
|
58
63
|
ws,
|
|
59
|
-
client:
|
|
64
|
+
client: this.clientFor(ws)
|
|
60
65
|
});
|
|
61
66
|
}
|
|
62
67
|
if (!this.deliveryAuthorizer) {
|
|
@@ -83,18 +88,17 @@ var CfRoomRegistry = class {
|
|
|
83
88
|
return this.ctx.getWebSockets().filter((ws) => this.attachmentOf(ws).rooms.includes(room));
|
|
84
89
|
}
|
|
85
90
|
attachmentOf(ws) {
|
|
86
|
-
return ws
|
|
87
|
-
connId: "",
|
|
88
|
-
state: "rejected",
|
|
89
|
-
path: "",
|
|
90
|
-
rooms: [],
|
|
91
|
-
data: {}
|
|
92
|
-
};
|
|
91
|
+
return socketAttachment(ws) ?? rejectedAttachment();
|
|
93
92
|
}
|
|
94
93
|
/** Reconstruct a `WsClient` for a raw socket (e.g. inside gateway lifecycle scans). */
|
|
95
94
|
clientFor(ws) {
|
|
96
95
|
this.reconcileFrameLimit(ws, this.attachmentOf(ws));
|
|
97
|
-
|
|
96
|
+
let gate = this.#sendGates.get(ws);
|
|
97
|
+
if (!gate) {
|
|
98
|
+
gate = new WebSocketSendGate(this.#sendPolicyForPath?.(this.attachmentOf(ws).path));
|
|
99
|
+
this.#sendGates.set(ws, gate);
|
|
100
|
+
}
|
|
101
|
+
return new CfWsClient(this.ctx, ws, gate);
|
|
98
102
|
}
|
|
99
103
|
reconcileFrameLimit(ws, attachment) {
|
|
100
104
|
const expected = this.frameLimitForPath?.(attachment.path);
|
|
@@ -125,6 +129,7 @@ var DoWebSocketHost = class {
|
|
|
125
129
|
dispatcher;
|
|
126
130
|
registry;
|
|
127
131
|
gatewayPaths;
|
|
132
|
+
#messages = /* @__PURE__ */ new WeakMap();
|
|
128
133
|
constructor(ctx, dispatcher, registry, gatewayPaths = []) {
|
|
129
134
|
this.ctx = ctx;
|
|
130
135
|
this.dispatcher = dispatcher;
|
|
@@ -147,6 +152,7 @@ var DoWebSocketHost = class {
|
|
|
147
152
|
if (principal !== void 0 && (expiresAtMs === void 0 || userId !== principal.subject || !principal.issuer || !principal.tenantId) || expiresAtMs !== void 0 && principal === void 0) return false;
|
|
148
153
|
const connId = crypto.randomUUID();
|
|
149
154
|
const attachment = {
|
|
155
|
+
version: 1,
|
|
150
156
|
connId,
|
|
151
157
|
state: "pending",
|
|
152
158
|
userId,
|
|
@@ -202,10 +208,21 @@ var DoWebSocketHost = class {
|
|
|
202
208
|
this.reject(ws, "Connection is not authorized");
|
|
203
209
|
return;
|
|
204
210
|
}
|
|
205
|
-
|
|
206
|
-
|
|
211
|
+
let queue = this.#messages.get(ws);
|
|
212
|
+
if (!queue) {
|
|
213
|
+
const path = socketAttachment(ws)?.path;
|
|
214
|
+
const options = this.dispatcher.collectEntrypoints().find((entry) => entry.meta.path === path)?.meta.options;
|
|
215
|
+
queue = new WsMessageQueue(() => this.reject(ws, "WebSocket message budget exceeded", 1013), options?.maxPendingMessages, options?.maxPendingBytes);
|
|
216
|
+
this.#messages.set(ws, queue);
|
|
217
|
+
}
|
|
218
|
+
await queue.run(message, async () => {
|
|
219
|
+
if (!this.isActive(ws)) return;
|
|
220
|
+
const client = this.registry.clientFor(ws);
|
|
221
|
+
await this.dispatcher.dispatchMessage(client.path, client, message);
|
|
222
|
+
});
|
|
207
223
|
}
|
|
208
224
|
async onClose(ws, code, reason) {
|
|
225
|
+
this.#messages.get(ws)?.stop();
|
|
209
226
|
if (!this.isActive(ws, false)) {
|
|
210
227
|
this.transition(ws, "rejected");
|
|
211
228
|
try {
|
|
@@ -235,7 +252,7 @@ var DoWebSocketHost = class {
|
|
|
235
252
|
}
|
|
236
253
|
transition(ws, state, expectedState) {
|
|
237
254
|
try {
|
|
238
|
-
const attachment = ws
|
|
255
|
+
const attachment = socketAttachment(ws);
|
|
239
256
|
if (!attachment) return false;
|
|
240
257
|
if (expectedState !== void 0 && attachment.state !== expectedState) return false;
|
|
241
258
|
attachment.state = state;
|
|
@@ -247,17 +264,18 @@ var DoWebSocketHost = class {
|
|
|
247
264
|
}
|
|
248
265
|
isActive(ws, checkExpiry = true) {
|
|
249
266
|
try {
|
|
250
|
-
const attachment = ws
|
|
267
|
+
const attachment = socketAttachment(ws);
|
|
251
268
|
if (!attachment || attachment.state !== "active") return false;
|
|
252
269
|
return !checkExpiry || attachment.expiresAtMs === void 0 || Number.isSafeInteger(attachment.expiresAtMs) && attachment.expiresAtMs > Date.now();
|
|
253
270
|
} catch {
|
|
254
271
|
return false;
|
|
255
272
|
}
|
|
256
273
|
}
|
|
257
|
-
reject(ws, reason) {
|
|
274
|
+
reject(ws, reason, code = 1008) {
|
|
275
|
+
this.#messages.get(ws)?.stop();
|
|
258
276
|
this.transition(ws, "rejected");
|
|
259
277
|
try {
|
|
260
|
-
ws.close(
|
|
278
|
+
ws.close(code, reason);
|
|
261
279
|
} catch {}
|
|
262
280
|
}
|
|
263
281
|
};
|
|
@@ -292,12 +310,13 @@ async function buildDoRuntime(rootModule, ctx, options) {
|
|
|
292
310
|
await app.callOnApplicationBootstrap();
|
|
293
311
|
const wsEntrypoints = app.entrypoints.ofKind("websocket", readWsEntrypointMeta);
|
|
294
312
|
const dispatcher = wsEntrypoints[0]?.meta.dispatcher ?? app.get(WsDispatcher);
|
|
313
|
+
registry.setSendPolicyResolver((path) => wsEntrypoints.find((entry) => entry.meta.path === path)?.meta.options.sendPolicy);
|
|
295
314
|
registry.setFrameLimitResolver((path) => dispatcher.getGatewayMaxFrameBytes(path));
|
|
296
315
|
registry.setDeliveryAuthorizer((client) => {
|
|
297
316
|
const path = client instanceof CfWsClient ? client.path : "";
|
|
298
317
|
return dispatcher.authorizeDelivery(path, client);
|
|
299
318
|
});
|
|
300
|
-
const live = initDoLive(app, ctx);
|
|
319
|
+
const live = initDoLive(app, ctx, registry);
|
|
301
320
|
return {
|
|
302
321
|
dispatcher,
|
|
303
322
|
registry,
|
|
@@ -321,7 +340,7 @@ function isIdentityField(value) {
|
|
|
321
340
|
* (matching their `wrangler.toml` `class_name`) built from their `AppModule`:
|
|
322
341
|
*
|
|
323
342
|
* ```ts
|
|
324
|
-
* export class ChatRoom extends VelaWebSocketDurableObject(AppModule) {}
|
|
343
|
+
* export class ChatRoom extends VelaWebSocketDurableObject(AppModule, { envToken: ENV }) {}
|
|
325
344
|
* ```
|
|
326
345
|
*
|
|
327
346
|
* It owns the raw hibernation socket lifecycle (Hono's `upgradeWebSocket` cannot
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"durable-objects.js","names":[],"sources":["../src/websocket/cf-room-registry.ts","../src/websocket/do-websocket-host.ts","../src/websocket/do-bootstrap.ts","../src/websocket/websocket.durable-object.ts","../src/nonce/nonce.durable-object.ts"],"sourcesContent":["import type { BroadcastCommand, RoomRegistry, WsClient } from '@velajs/vela/websocket';\nimport type { DoStateLike, WsAttachment, WsLike } from './do-state';\nimport { CfWsClient } from './cf-ws-client';\n\n/**\n * Core `RoomRegistry` backed by a Durable Object's hibernatable sockets. Room\n * membership lives in tags (hub room, set at accept) + the attachment (dynamic\n * `join()`), never in DO instance fields — so it survives hibernation with zero\n * rehydration.\n */\nexport class CfRoomRegistry implements RoomRegistry {\n private deliveryAuthorizer?: (client: WsClient) => boolean | Promise<boolean>;\n private frameLimitForPath?: (path: string) => number | undefined;\n\n constructor(private readonly ctx: DoStateLike) {}\n\n setDeliveryAuthorizer(authorizer: (client: WsClient) => boolean | Promise<boolean>): void {\n this.deliveryAuthorizer = authorizer;\n }\n\n /** Reconcile pre-migration/woken attachments with authoritative gateway metadata. */\n setFrameLimitResolver(resolver: (path: string) => number | undefined): void {\n this.frameLimitForPath = resolver;\n }\n\n // CF sockets are registered with the DO by `acceptWebSocket`; nothing to track.\n register(_client: WsClient): void {}\n leaveAll(_client: WsClient): void {}\n\n join(client: WsClient, room: string): void | Promise<void> {\n return client.join(room);\n }\n\n leave(client: WsClient, room: string): void | Promise<void> {\n return client.leave(room);\n }\n\n localIdsInRoom(room: string): string[] {\n return this.socketsForRoom(room).map((ws) => this.attachmentOf(ws).connId);\n }\n\n deliverLocal(cmd: BroadcastCommand): void | Promise<void> {\n const excludeIds = new Set(cmd.exceptIds ?? []);\n const excludeRooms = cmd.exceptRooms ?? [];\n\n // Empty `rooms` => GLOBAL (every socket in this DO). Otherwise the union of\n // targeted rooms, deduped per connection.\n const targets =\n cmd.rooms.length === 0 ? this.ctx.getWebSockets() : this.collectRooms(cmd.rooms);\n\n const seen = new Set<string>();\n const selected: Array<{ ws: WsLike; client: WsClient }> = [];\n for (const ws of targets) {\n const att = this.reconcileFrameLimit(ws, this.attachmentOf(ws));\n if (seen.has(att.connId)) continue;\n seen.add(att.connId);\n if (\n att.state !== 'active' ||\n (att.expiresAtMs !== undefined &&\n (!Number.isSafeInteger(att.expiresAtMs) || att.expiresAtMs <= Date.now()))\n ) {\n try {\n att.state = 'rejected';\n ws.serializeAttachment(att);\n ws.close(1008, 'identity expired or connection rejected');\n } catch {\n // already closed or malformed attachment\n }\n continue;\n }\n if (excludeIds.has(att.connId)) continue;\n if (excludeRooms.some((r) => att.rooms.includes(r))) continue;\n selected.push({ ws, client: new CfWsClient(this.ctx, ws) });\n }\n\n if (!this.deliveryAuthorizer) {\n for (const { client } of selected) client.sendRaw(cmd.frame);\n return;\n }\n return Promise.all(\n selected.map(async ({ ws, client }) => {\n let allowed = false;\n try {\n allowed = (await this.deliveryAuthorizer!(client)) === true;\n } catch {\n allowed = false;\n }\n if (allowed) client.sendRaw(cmd.frame);\n else this.reject(ws, 'delivery authorization revoked');\n }),\n ).then(() => undefined);\n }\n\n private collectRooms(rooms: string[]): WsLike[] {\n const set = new Set<WsLike>();\n for (const room of rooms) for (const ws of this.socketsForRoom(room)) set.add(ws);\n return [...set];\n }\n\n private socketsForRoom(room: string): WsLike[] {\n // Membership is the attachment's `rooms`, NOT the hibernation tag: tags are\n // immutable after acceptWebSocket, so a socket that left its hub room still\n // carries the tag. Since one DO ≈ one room, scanning all sockets in the DO\n // and filtering by attachment is both correct and cheap.\n return this.ctx.getWebSockets().filter((ws) => this.attachmentOf(ws).rooms.includes(room));\n }\n\n private attachmentOf(ws: WsLike): WsAttachment {\n return (\n (ws.deserializeAttachment() as WsAttachment | null) ?? {\n connId: '',\n state: 'rejected',\n path: '',\n rooms: [],\n data: {},\n }\n );\n }\n\n /** Reconstruct a `WsClient` for a raw socket (e.g. inside gateway lifecycle scans). */\n clientFor(ws: WsLike): CfWsClient {\n this.reconcileFrameLimit(ws, this.attachmentOf(ws));\n return new CfWsClient(this.ctx, ws);\n }\n\n private reconcileFrameLimit(ws: WsLike, attachment: WsAttachment): WsAttachment {\n const expected = this.frameLimitForPath?.(attachment.path);\n if (expected !== undefined && attachment.maxFrameBytes !== expected) {\n attachment.maxFrameBytes = expected;\n ws.serializeAttachment(attachment);\n }\n return attachment;\n }\n\n private reject(ws: WsLike, reason: string): void {\n try {\n const attachment = this.attachmentOf(ws);\n attachment.state = 'rejected';\n ws.serializeAttachment(attachment);\n ws.close(1008, reason);\n } catch {\n // already closed or malformed attachment\n }\n }\n}\n","import { assertBroadcastCommandFits } from '@velajs/vela/websocket';\nimport type { BroadcastCommand, WsDispatcher } from '@velajs/vela/websocket';\nimport type { CfRoomRegistry } from './cf-room-registry';\nimport {\n MAX_WS_ATTACHMENT_BYTES,\n type DoStateLike,\n type WsAttachment,\n type WsLike,\n} from './do-state';\nimport { connTag, roomTag } from './room-id';\n\nexport interface WsConnectionPrincipal {\n issuer: string;\n subject: string;\n principalType: 'user' | 'service';\n tenantId: string;\n}\n\n/**\n * The socket lifecycle inside a WebSocket Durable Object, decoupled from the\n * `cloudflare:workers` base class so it is unit-testable with fakes. The thin\n * `VelaWebSocketDurableObject` shell forwards its hibernation callbacks here.\n */\nexport class DoWebSocketHost {\n constructor(\n private readonly ctx: DoStateLike,\n private readonly dispatcher: WsDispatcher,\n private readonly registry: CfRoomRegistry,\n private readonly gatewayPaths: readonly string[] = [],\n ) {}\n\n /** The single registered gateway's path — a fallback when the Worker didn't forward `x-vela-path`. */\n defaultPath(): string | undefined {\n return this.gatewayPaths[0];\n }\n\n /**\n * Accept a hibernatable socket: tag it with its hub room + connection id,\n * persist the attachment, then await `OnGatewayConnection` before the caller\n * returns 101. A rejected lifecycle hook closes and fails the upgrade.\n */\n async accept(\n ws: WsLike,\n path: string,\n roomId: string,\n userId?: string,\n expiresAtMs?: number,\n principal?: WsConnectionPrincipal,\n ): Promise<boolean> {\n const maxFrameBytes = this.dispatcher.getGatewayMaxFrameBytes(path);\n if (maxFrameBytes === undefined) return false;\n if (\n expiresAtMs !== undefined &&\n (!Number.isSafeInteger(expiresAtMs) || expiresAtMs <= Date.now())\n ) {\n return false;\n }\n if (\n (principal !== undefined &&\n (expiresAtMs === undefined ||\n userId !== principal.subject ||\n !principal.issuer ||\n !principal.tenantId)) ||\n (expiresAtMs !== undefined && principal === undefined)\n ) {\n return false;\n }\n\n const connId = crypto.randomUUID();\n const attachment: WsAttachment = {\n connId,\n state: 'pending',\n userId,\n principal:\n principal === undefined\n ? undefined\n : {\n issuer: principal.issuer,\n subject: principal.subject,\n principalType: principal.principalType,\n },\n tenantId: principal?.tenantId,\n expiresAtMs,\n path,\n maxFrameBytes,\n rooms: [roomId],\n data: {\n ...(userId ? { userId } : {}),\n ...(principal\n ? {\n principal: {\n issuer: principal.issuer,\n subject: principal.subject,\n principalType: principal.principalType,\n },\n tenantId: principal.tenantId,\n }\n : {}),\n ...(expiresAtMs !== undefined ? { expiresAtMs } : {}),\n },\n };\n if (new TextEncoder().encode(JSON.stringify(attachment)).byteLength > MAX_WS_ATTACHMENT_BYTES) {\n return false;\n }\n try {\n this.ctx.acceptWebSocket(ws, [roomTag(roomId), connTag(connId)]);\n ws.serializeAttachment(attachment);\n } catch {\n try {\n ws.close(1008, 'Connection rejected');\n } catch {\n // already closed\n }\n return false;\n }\n\n const client = this.registry.clientFor(ws);\n try {\n await this.dispatcher.handleOpen(path, client);\n // Another callback may have rejected the socket while the asynchronous\n // connection hook was still pending. Never resurrect that terminal state\n // after the hook resolves.\n if (!this.transition(ws, 'active', 'pending')) {\n throw new Error('Unable to persist authorized WebSocket state');\n }\n return true;\n } catch (err) {\n await this.dispatcher.handleError(path, client, err).catch(() => {});\n this.transition(ws, 'rejected');\n try {\n ws.close(1008, 'Connection rejected');\n } catch {\n // already closed\n }\n return false;\n }\n }\n\n async onMessage(ws: WsLike, message: string | ArrayBuffer): Promise<void> {\n if (!this.isActive(ws)) {\n this.reject(ws, 'Connection is not authorized');\n return;\n }\n const client = this.registry.clientFor(ws);\n await this.dispatcher.dispatchMessage(client.path, client, message);\n }\n\n async onClose(ws: WsLike, code: number, reason: string): Promise<void> {\n if (!this.isActive(ws, false)) {\n this.transition(ws, 'rejected');\n try {\n ws.close();\n } catch {\n // already closed\n }\n return;\n }\n const client = this.registry.clientFor(ws);\n await this.dispatcher.handleClose(client.path, client, code, reason);\n // Complete the closing handshake. Only 1000 and 3000-4999 are valid for\n // close(); reserved/abnormal codes (1005/1006/1015, etc.) throw a RangeError,\n // so fall back to a codeless close on those.\n try {\n if (code === 1000 || (code >= 3000 && code <= 4999)) ws.close(code, reason);\n else ws.close();\n } catch {\n // socket already closed\n }\n }\n\n async onError(ws: WsLike, err: unknown): Promise<void> {\n if (!this.isActive(ws, false)) {\n this.reject(ws, 'Connection failed before authorization');\n return;\n }\n const client = this.registry.clientFor(ws);\n await this.dispatcher.handleError(client.path, client, err);\n }\n\n /** Deliver a broadcast command to this DO's local sockets (RPC entry point). */\n broadcast(cmd: BroadcastCommand): void | Promise<void> {\n assertBroadcastCommandFits(cmd, this.dispatcher.getMaximumGatewayFrameBytes());\n return this.registry.deliverLocal(cmd);\n }\n\n private transition(\n ws: WsLike,\n state: WsAttachment['state'],\n expectedState?: WsAttachment['state'],\n ): boolean {\n try {\n const attachment = ws.deserializeAttachment() as WsAttachment | null;\n if (!attachment) return false;\n if (expectedState !== undefined && attachment.state !== expectedState) return false;\n attachment.state = state;\n ws.serializeAttachment(attachment);\n return true;\n } catch {\n return false;\n }\n }\n\n private isActive(ws: WsLike, checkExpiry = true): boolean {\n try {\n const attachment = ws.deserializeAttachment() as WsAttachment | null;\n if (!attachment || attachment.state !== 'active') return false;\n return (\n !checkExpiry ||\n attachment.expiresAtMs === undefined ||\n (Number.isSafeInteger(attachment.expiresAtMs) && attachment.expiresAtMs > Date.now())\n );\n } catch {\n return false;\n }\n }\n\n private reject(ws: WsLike, reason: string): void {\n this.transition(ws, 'rejected');\n try {\n ws.close(1008, reason);\n } catch {\n // already closed\n }\n }\n}\n","import { bootstrap, VelaApplication } from '@velajs/vela';\nimport type { InjectionToken, Type } from '@velajs/vela';\nimport {\n local,\n readWsEntrypointMeta,\n WsDispatcher,\n WsServerImpl,\n WS_SERVER,\n} from '@velajs/vela/websocket';\nimport type { WsServer } from '@velajs/vela/websocket';\nimport type { LiveEngine } from '@velajs/vela/live';\nimport { registerCloudflareEnvironment } from '../environment';\nimport { CfWsClient } from './cf-ws-client';\nimport { CfRoomRegistry } from './cf-room-registry';\nimport { initDoLive, initializeDoLiveResources } from './do-live';\nimport type { DoStateLike } from './do-state';\nimport { WsServerHolder } from './ws-server-holder';\n\nexport interface DoRuntime {\n dispatcher: WsDispatcher;\n registry: CfRoomRegistry;\n server: WsServer;\n /** Gateway paths from `app.entrypoints.ofKind('websocket')` (discovery order). */\n gatewayPaths: string[];\n /** The live-query engine (undefined when the app doesn't import LiveModule). */\n live?: LiveEngine;\n close(signal?: string): Promise<void>;\n}\n\n/**\n * Slim DI bootstrap for the Durable Object isolate: wires the container and runs\n * `OnModuleInit`/`OnApplicationBootstrap` (so `WsDispatcher` discovers gateways)\n * WITHOUT building the Hono app/routes the DO never serves. Cloudflare binding\n * refs are initialized straight from the DO's `env`, and the ctx-backed server\n * is bound before bootstrap lifecycle so gateway `afterInit`/handlers see it.\n */\nexport async function buildDoRuntime<T extends object>(\n rootModule: Type,\n ctx: DoStateLike,\n options: { env: T; envToken: InjectionToken<T> },\n): Promise<DoRuntime> {\n const { container, routeManager, loader } = await bootstrap(rootModule, {\n configureContainer: (container) => {\n registerCloudflareEnvironment(container, { token: options.envToken, env: options.env });\n },\n });\n\n const registry = new CfRoomRegistry(ctx);\n const driver = local();\n driver.bind(registry);\n const server = new WsServerImpl(driver);\n\n if (container.has(WS_SERVER)) {\n const holder = await container.resolveAsync(WS_SERVER);\n if (holder instanceof WsServerHolder) holder.setTarget(server);\n }\n\n const app = new VelaApplication(container, routeManager);\n app.setInstances(await loader.resolveAllInstances());\n initializeDoLiveResources(container, ctx);\n await app.callOnModuleInit();\n await app.callOnApplicationBootstrap();\n\n // The entrypoint registry is the transport contract: one 'websocket' entry\n // per discovered gateway ({ meta: { path, dispatcher } }). Built by\n // callOnApplicationBootstrap(), so this slim no-routes path has it too.\n const wsEntrypoints = app.entrypoints.ofKind('websocket', readWsEntrypointMeta);\n const dispatcher = wsEntrypoints[0]?.meta.dispatcher ?? app.get(WsDispatcher);\n registry.setFrameLimitResolver((path) => dispatcher.getGatewayMaxFrameBytes(path));\n registry.setDeliveryAuthorizer((client) => {\n const path = client instanceof CfWsClient ? client.path : '';\n return dispatcher.authorizeDelivery(path, client);\n });\n\n // Live queries: wire the SQLite cursor log + local driver mode and replay\n // hibernation-persisted subscriptions into the fresh engine.\n const live = initDoLive(app, ctx);\n\n return {\n // Zero gateways still yields a live dispatcher (module imported, nothing\n // decorated) — fall back to resolving it directly.\n dispatcher,\n registry,\n server,\n gatewayPaths: wsEntrypoints.map((ep) => ep.meta.path),\n live,\n close: (signal?: string) => app.close(signal),\n };\n}\n","import { DurableObject } from 'cloudflare:workers';\nimport type { InjectionToken } from '@velajs/vela';\nimport type { BroadcastCommand } from '@velajs/vela/websocket';\nimport type {\n CommitStamp,\n InvalidationCommand,\n LiveEngine,\n LiveInspection,\n} from '@velajs/vela/live';\nimport { buildDoRuntime } from './do-bootstrap';\nimport { DoWebSocketHost, type WsConnectionPrincipal } from './do-websocket-host';\nimport { armDoPitr, readDoPitrBookmark } from './do-pitr';\nimport { resolveCloudflareRoot } from '../root-module';\nimport type { CloudflareRoot } from '../root-module';\nimport type {\n DoPitrArmOptions,\n DoPitrArmResult,\n DoPitrBookmarkRead,\n VelaDoPitrRpc,\n} from './do-pitr';\n\nconst PING = '{\"event\":\"$ping\"}';\nconst PONG = '{\"event\":\"$pong\"}';\nconst MAX_IDENTITY_FIELD_BYTES = 2048;\nconst encoder = new TextEncoder();\n\nfunction isIdentityField(value: string | null): value is string {\n return (\n value !== null &&\n value.length > 0 &&\n encoder.encode(value).byteLength <= MAX_IDENTITY_FIELD_BYTES\n );\n}\n\n/**\n * Base class for the WebSocket Durable Object. The user exports a named subclass\n * (matching their `wrangler.toml` `class_name`) built from their `AppModule`:\n *\n * ```ts\n * export class ChatRoom extends VelaWebSocketDurableObject(AppModule) {}\n * ```\n *\n * It owns the raw hibernation socket lifecycle (Hono's `upgradeWebSocket` cannot\n * bridge DO hibernation) and forwards every event into the runtime-agnostic\n * `WsDispatcher` via {@link DoWebSocketHost}.\n */\nexport function VelaWebSocketDurableObject<T extends object>(\n rootModule: CloudflareRoot<NoInfer<T>>,\n options: { envToken: InjectionToken<T> },\n): new (\n ctx: DurableObjectState,\n env: T,\n) => DurableObject<T> &\n VelaDoPitrRpc & {\n broadcast(cmd: BroadcastCommand): Promise<void>;\n invalidate(cmd: InvalidationCommand): Promise<CommitStamp | undefined>;\n inspectLive(): Promise<LiveInspection>;\n } {\n return class VelaWsDurableObject extends DurableObject<T> {\n private host!: DoWebSocketHost;\n private liveEngine?: LiveEngine;\n private readonly ready: Promise<void>;\n\n constructor(ctx: DurableObjectState, env: T) {\n super(ctx, env);\n // Application-level ping/pong answered WITHOUT waking a hibernated DO.\n try {\n ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(PING, PONG));\n } catch {\n // Older runtimes without auto-response — fine, protocol pings still work.\n }\n this.ready = ctx.blockConcurrencyWhile(async () => {\n const runtime = await buildDoRuntime(resolveCloudflareRoot(rootModule, env), ctx, {\n ...options,\n env,\n });\n this.host = new DoWebSocketHost(\n ctx,\n runtime.dispatcher,\n runtime.registry,\n runtime.gatewayPaths,\n );\n this.liveEngine = runtime.live;\n });\n }\n\n /** Intra-worker RPC only; HTTP fetch never exposes this admin snapshot. */\n async inspectLive(): Promise<LiveInspection> {\n await this.ready;\n return this.liveEngine?.inspect() ?? { subscriptions: [], rooms: [] };\n }\n\n override async fetch(request: Request): Promise<Response> {\n await this.ready;\n if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket') {\n return new Response('Expected WebSocket upgrade', { status: 426 });\n }\n\n const url = new URL(request.url);\n // Headers are primary (carry auth + multi-gateway routing); fall back to the\n // DO's own name (set via idFromName(room)) and the single gateway path so a\n // plain `stub.fetch(request)` forward still works.\n const roomId = request.headers.get('x-vela-room') ?? this.ctx.id.name ?? url.pathname;\n const path = request.headers.get('x-vela-path') ?? this.host.defaultPath() ?? url.pathname;\n const userId = request.headers.get('x-vela-user') || undefined;\n const rawExpiresAtMs = request.headers.get('x-vela-expires-at-ms');\n const parsedExpiresAtMs = rawExpiresAtMs === null ? undefined : Number(rawExpiresAtMs);\n if (\n parsedExpiresAtMs !== undefined &&\n (!Number.isSafeInteger(parsedExpiresAtMs) || parsedExpiresAtMs <= 0)\n ) {\n return new Response('Invalid WebSocket identity expiry', { status: 403 });\n }\n\n const issuer = request.headers.get('x-vela-issuer');\n const subject = request.headers.get('x-vela-subject');\n const principalType = request.headers.get('x-vela-principal-type');\n const tenantId = request.headers.get('x-vela-tenant');\n const hasPrincipalHeader =\n issuer !== null || subject !== null || principalType !== null || tenantId !== null;\n let principal: WsConnectionPrincipal | undefined;\n if (hasPrincipalHeader) {\n if (\n !isIdentityField(issuer) ||\n !isIdentityField(subject) ||\n (principalType !== 'user' && principalType !== 'service') ||\n !isIdentityField(tenantId) ||\n parsedExpiresAtMs === undefined ||\n (userId !== undefined && userId !== subject)\n ) {\n return new Response('Invalid WebSocket principal', { status: 403 });\n }\n principal = { issuer, subject, principalType, tenantId };\n } else if (parsedExpiresAtMs !== undefined) {\n return new Response('WebSocket identity tuple is missing', { status: 403 });\n }\n\n if (parsedExpiresAtMs !== undefined && parsedExpiresAtMs <= Date.now()) {\n return new Response('WebSocket identity expired', { status: 403 });\n }\n\n const { 0: client, 1: server } = new WebSocketPair();\n const accepted = await this.host.accept(\n server,\n path,\n roomId,\n userId,\n parsedExpiresAtMs,\n principal,\n );\n if (!accepted) return new Response('WebSocket connection rejected', { status: 403 });\n return new Response(null, { status: 101, webSocket: client });\n }\n\n override async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {\n await this.ready;\n await this.host.onMessage(ws, message);\n }\n\n override async webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void> {\n await this.ready;\n await this.host.onClose(ws, code, reason);\n }\n\n override async webSocketError(ws: WebSocket, error: unknown): Promise<void> {\n await this.ready;\n await this.host.onError(ws, error);\n }\n\n /** DO RPC — server-initiated broadcast forwarded from a Worker (see `broadcastToRoom`). */\n async broadcast(cmd: BroadcastCommand): Promise<void> {\n await this.ready;\n await this.host.broadcast(cmd);\n }\n\n /**\n * DO RPC — live tag invalidation forwarded from a Worker (durableObjectLive /\n * liveInvalidateToRoom). Appends to THIS log scope's cursor log and returns\n * the commit stamp (what `Vela-Commit-Cursor` carries); the subscription\n * refreshes fan out asynchronously.\n */\n async invalidate(cmd: InvalidationCommand): Promise<CommitStamp | undefined> {\n await this.ready;\n return this.liveEngine?.applyInvalidation(cmd);\n }\n\n // -- Durable Object PITR (point-in-time recovery) RPC ---------------------\n //\n // GUARD / reachability: these are admin-privileged operations (a restore can\n // roll the DO's SQLite state back up to 30 days and, with `restart`, abort\n // the DO to apply it now). They are safe to expose here because a Durable\n // Object's RPC methods are NOT network-reachable: a `DurableObjectStub` is\n // obtainable ONLY from a Worker that binds this DO namespace, and RPC calls\n // travel Cloudflare's internal capability channel — never the public\n // internet. A WebSocket/HTTP client reaches only `fetch()` above, never these\n // methods. The security boundary is therefore the WORKER-SIDE studio admin\n // gate that fronts the `CloudflareDoTimeTravelPort` (master admin token + the\n // 428 confirm challenge) — the identical trust model as `broadcast()` /\n // `invalidate()` on this same DO. See `do-pitr.ts` for the storage wrappers.\n\n /** DO RPC — the current PITR bookmark (typed-unavailable on a non-SQLite DO). */\n async pitrCurrentBookmark(): Promise<DoPitrBookmarkRead> {\n await this.ready;\n return readDoPitrBookmark(this.ctx.storage);\n }\n\n /** DO RPC — the PITR bookmark closest to `time` (+ the current bookmark). */\n async pitrBookmarkForTime(time: number | string): Promise<DoPitrBookmarkRead> {\n await this.ready;\n return readDoPitrBookmark(this.ctx.storage, time);\n }\n\n /**\n * DO RPC — arm a PITR restore and return the undo bookmark. When `restart` is\n * requested, `ctx.abort()` is called AFTER the undo bookmark is computed so\n * the recovery applies on the immediately-following session; otherwise the\n * restore applies on the next natural restart (`restarted: false`).\n */\n async pitrArmRestore(opts: DoPitrArmOptions): Promise<DoPitrArmResult> {\n await this.ready;\n const result = await armDoPitr(this.ctx.storage, opts);\n if (opts.restart === true) this.ctx.abort('vela PITR restore');\n return result;\n }\n };\n}\n","import { DurableObject } from 'cloudflare:workers';\nimport { MAX_NONCE_BYTES, isCanonicalBoundedText, isValidExpiry } from './nonce-validation';\nconst EXPIRED_DELETE_BATCH = 1_024;\n\nconst CREATE_TABLE =\n 'CREATE TABLE IF NOT EXISTS __vela_nonce_claims (' +\n 'nonce TEXT PRIMARY KEY NOT NULL, ' +\n 'expires_at INTEGER NOT NULL CHECK (expires_at > 0)' +\n ') WITHOUT ROWID';\nconst CREATE_EXPIRY_INDEX =\n 'CREATE INDEX IF NOT EXISTS __vela_nonce_claims_expiry ON __vela_nonce_claims (expires_at)';\nconst DELETE_EXPIRED =\n 'DELETE FROM __vela_nonce_claims WHERE nonce IN (' +\n 'SELECT nonce FROM __vela_nonce_claims ' +\n 'WHERE expires_at < ? ORDER BY expires_at LIMIT ?' +\n ')';\nconst INSERT_CLAIM =\n 'INSERT INTO __vela_nonce_claims (nonce, expires_at) VALUES (?, ?) ' +\n 'ON CONFLICT(nonce) DO NOTHING RETURNING nonce, expires_at';\n\ninterface NonceSqlCursor {\n toArray(): Record<string, unknown>[];\n}\n\ninterface NonceSqlStorage {\n exec(query: string, ...bindings: unknown[]): NonceSqlCursor;\n}\n\n/**\n * SQLite Durable Object that atomically consumes nonces.\n *\n * Export this class from the Worker entry and register it through a Wrangler\n * `new_sqlite_classes` migration. `INSERT ... ON CONFLICT DO NOTHING RETURNING`\n * is the single-use decision; all SQL runs synchronously before the RPC method\n * yields, and the nonce primary key is the final concurrency boundary.\n */\nexport class VelaNonceDurableObject extends DurableObject<Record<string, unknown>> {\n private sql?: NonceSqlStorage;\n\n constructor(ctx: DurableObjectState, env: Record<string, unknown>) {\n super(ctx, env);\n try {\n const sql = ctx.storage?.sql;\n if (!sql || typeof sql.exec !== 'function') return;\n sql.exec(CREATE_TABLE);\n sql.exec(CREATE_EXPIRY_INDEX);\n this.sql = sql;\n } catch {\n // A non-SQLite/misconfigured class stays fail-closed: every claim denies.\n this.sql = undefined;\n }\n }\n\n async claim(nonce: string, expEpochSeconds: number): Promise<boolean> {\n const sql = this.sql;\n if (!sql) return false;\n\n const now = Math.floor(Date.now() / 1_000);\n if (!isCanonicalBoundedText(nonce, MAX_NONCE_BYTES) || !isValidExpiry(expEpochSeconds, now)) {\n return false;\n }\n\n try {\n // Keep cleanup bounded so one claim cannot monopolize the DO. Entries at\n // exactly `now` remain: Vela's invocation verifier accepts `now === exp`.\n sql.exec(DELETE_EXPIRED, now, EXPIRED_DELETE_BATCH);\n\n const cursor = sql.exec(INSERT_CLAIM, nonce, expEpochSeconds);\n if (!cursor || typeof cursor.toArray !== 'function') return false;\n const rows: unknown = cursor.toArray();\n if (!Array.isArray(rows) || rows.length !== 1) return false;\n\n const row: unknown = rows[0];\n if (typeof row !== 'object' || row === null) return false;\n if (\n !Object.hasOwn(row, 'nonce') ||\n !Object.hasOwn(row, 'expires_at') ||\n !('nonce' in row) ||\n !('expires_at' in row)\n ) {\n return false;\n }\n return row.nonce === nonce && row.expires_at === expEpochSeconds;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"durable-objects.js","names":[],"sources":["../src/websocket/cf-room-registry.ts","../src/websocket/do-websocket-host.ts","../src/websocket/do-bootstrap.ts","../src/websocket/websocket.durable-object.ts","../src/nonce/nonce.durable-object.ts"],"sourcesContent":["import { WebSocketSendGate, type WebSocketSendPolicy } from '@velajs/vela/websocket';\nimport { socketAttachment, rejectedAttachment } from './ws-attachment';\nimport type { BroadcastCommand, RoomRegistry, WsClient } from '@velajs/vela/websocket';\nimport type { DoStateLike, WsAttachment, WsLike } from './do-state';\nimport { CfWsClient } from './cf-ws-client';\n\n/**\n * Core `RoomRegistry` backed by a Durable Object's hibernatable sockets. Room\n * membership lives in tags (hub room, set at accept) + the attachment (dynamic\n * `join()`), never in DO instance fields — so it survives hibernation with zero\n * rehydration.\n */\nexport class CfRoomRegistry implements RoomRegistry {\n readonly #sendGates = new WeakMap<WsLike, WebSocketSendGate>();\n #sendPolicyForPath?: (path: string) => WebSocketSendPolicy | undefined;\n\n setSendPolicyResolver(resolve: (path: string) => WebSocketSendPolicy | undefined): void {\n this.#sendPolicyForPath = resolve;\n }\n\n private deliveryAuthorizer?: (client: WsClient) => boolean | Promise<boolean>;\n private frameLimitForPath?: (path: string) => number | undefined;\n\n constructor(private readonly ctx: DoStateLike) {}\n\n setDeliveryAuthorizer(authorizer: (client: WsClient) => boolean | Promise<boolean>): void {\n this.deliveryAuthorizer = authorizer;\n }\n\n /** Reconcile pre-migration/woken attachments with authoritative gateway metadata. */\n setFrameLimitResolver(resolver: (path: string) => number | undefined): void {\n this.frameLimitForPath = resolver;\n }\n\n // CF sockets are registered with the DO by `acceptWebSocket`; nothing to track.\n register(_client: WsClient): void {}\n leaveAll(_client: WsClient): void {}\n\n join(client: WsClient, room: string): void | Promise<void> {\n return client.join(room);\n }\n\n leave(client: WsClient, room: string): void | Promise<void> {\n return client.leave(room);\n }\n\n localIdsInRoom(room: string): string[] {\n return this.socketsForRoom(room).map((ws) => this.attachmentOf(ws).connId);\n }\n\n deliverLocal(cmd: BroadcastCommand): void | Promise<void> {\n const excludeIds = new Set(cmd.exceptIds ?? []);\n const excludeRooms = cmd.exceptRooms ?? [];\n\n // Empty `rooms` => GLOBAL (every socket in this DO). Otherwise the union of\n // targeted rooms, deduped per connection.\n const targets =\n cmd.rooms.length === 0 ? this.ctx.getWebSockets() : this.collectRooms(cmd.rooms);\n\n const seen = new Set<string>();\n const selected: Array<{ ws: WsLike; client: WsClient }> = [];\n for (const ws of targets) {\n const att = this.reconcileFrameLimit(ws, this.attachmentOf(ws));\n if (\n att.state !== 'active' ||\n (att.expiresAtMs !== undefined &&\n (!Number.isSafeInteger(att.expiresAtMs) || att.expiresAtMs <= Date.now()))\n ) {\n try {\n att.state = 'rejected';\n ws.serializeAttachment(att);\n ws.close(1008, 'identity expired or connection rejected');\n } catch {\n // already closed or malformed attachment\n }\n continue;\n }\n if (seen.has(att.connId)) continue;\n seen.add(att.connId);\n if (excludeIds.has(att.connId)) continue;\n if (excludeRooms.some((r) => att.rooms.includes(r))) continue;\n selected.push({ ws, client: this.clientFor(ws) });\n }\n\n if (!this.deliveryAuthorizer) {\n for (const { client } of selected) client.sendRaw(cmd.frame);\n return;\n }\n return Promise.all(\n selected.map(async ({ ws, client }) => {\n let allowed = false;\n try {\n allowed = (await this.deliveryAuthorizer!(client)) === true;\n } catch {\n allowed = false;\n }\n if (allowed) client.sendRaw(cmd.frame);\n else this.reject(ws, 'delivery authorization revoked');\n }),\n ).then(() => undefined);\n }\n\n private collectRooms(rooms: string[]): WsLike[] {\n const set = new Set<WsLike>();\n for (const room of rooms) for (const ws of this.socketsForRoom(room)) set.add(ws);\n return [...set];\n }\n\n private socketsForRoom(room: string): WsLike[] {\n // Membership is the attachment's `rooms`, NOT the hibernation tag: tags are\n // immutable after acceptWebSocket, so a socket that left its hub room still\n // carries the tag. Since one DO ≈ one room, scanning all sockets in the DO\n // and filtering by attachment is both correct and cheap.\n return this.ctx.getWebSockets().filter((ws) => this.attachmentOf(ws).rooms.includes(room));\n }\n\n private attachmentOf(ws: WsLike): WsAttachment {\n return socketAttachment(ws) ?? rejectedAttachment();\n }\n\n /** Reconstruct a `WsClient` for a raw socket (e.g. inside gateway lifecycle scans). */\n clientFor(ws: WsLike): CfWsClient {\n this.reconcileFrameLimit(ws, this.attachmentOf(ws));\n let gate = this.#sendGates.get(ws);\n if (!gate) {\n gate = new WebSocketSendGate(this.#sendPolicyForPath?.(this.attachmentOf(ws).path));\n this.#sendGates.set(ws, gate);\n }\n return new CfWsClient(this.ctx, ws, gate);\n }\n\n private reconcileFrameLimit(ws: WsLike, attachment: WsAttachment): WsAttachment {\n const expected = this.frameLimitForPath?.(attachment.path);\n if (expected !== undefined && attachment.maxFrameBytes !== expected) {\n attachment.maxFrameBytes = expected;\n ws.serializeAttachment(attachment);\n }\n return attachment;\n }\n\n private reject(ws: WsLike, reason: string): void {\n try {\n const attachment = this.attachmentOf(ws);\n attachment.state = 'rejected';\n ws.serializeAttachment(attachment);\n ws.close(1008, reason);\n } catch {\n // already closed or malformed attachment\n }\n }\n}\n","import { socketAttachment } from './ws-attachment';\nimport { WsMessageQueue, assertBroadcastCommandFits } from '@velajs/vela/websocket';\nimport type { BroadcastCommand, WsDispatcher } from '@velajs/vela/websocket';\nimport type { CfRoomRegistry } from './cf-room-registry';\nimport {\n MAX_WS_ATTACHMENT_BYTES,\n type DoStateLike,\n type WsAttachment,\n type WsLike,\n} from './do-state';\nimport { connTag, roomTag } from './room-id';\n\nexport interface WsConnectionPrincipal {\n issuer: string;\n subject: string;\n principalType: 'user' | 'service';\n tenantId: string;\n}\n\n/**\n * The socket lifecycle inside a WebSocket Durable Object, decoupled from the\n * `cloudflare:workers` base class so it is unit-testable with fakes. The thin\n * `VelaWebSocketDurableObject` shell forwards its hibernation callbacks here.\n */\nexport class DoWebSocketHost {\n readonly #messages = new WeakMap<WsLike, WsMessageQueue>();\n constructor(\n private readonly ctx: DoStateLike,\n private readonly dispatcher: WsDispatcher,\n private readonly registry: CfRoomRegistry,\n private readonly gatewayPaths: readonly string[] = [],\n ) {}\n\n /** The single registered gateway's path — a fallback when the Worker didn't forward `x-vela-path`. */\n defaultPath(): string | undefined {\n return this.gatewayPaths[0];\n }\n\n /**\n * Accept a hibernatable socket: tag it with its hub room + connection id,\n * persist the attachment, then await `OnGatewayConnection` before the caller\n * returns 101. A rejected lifecycle hook closes and fails the upgrade.\n */\n async accept(\n ws: WsLike,\n path: string,\n roomId: string,\n userId?: string,\n expiresAtMs?: number,\n principal?: WsConnectionPrincipal,\n ): Promise<boolean> {\n const maxFrameBytes = this.dispatcher.getGatewayMaxFrameBytes(path);\n if (maxFrameBytes === undefined) return false;\n if (\n expiresAtMs !== undefined &&\n (!Number.isSafeInteger(expiresAtMs) || expiresAtMs <= Date.now())\n ) {\n return false;\n }\n if (\n (principal !== undefined &&\n (expiresAtMs === undefined ||\n userId !== principal.subject ||\n !principal.issuer ||\n !principal.tenantId)) ||\n (expiresAtMs !== undefined && principal === undefined)\n ) {\n return false;\n }\n\n const connId = crypto.randomUUID();\n const attachment: WsAttachment = {\n version: 1,\n connId,\n state: 'pending',\n userId,\n principal:\n principal === undefined\n ? undefined\n : {\n issuer: principal.issuer,\n subject: principal.subject,\n principalType: principal.principalType,\n },\n tenantId: principal?.tenantId,\n expiresAtMs,\n path,\n maxFrameBytes,\n rooms: [roomId],\n data: {\n ...(userId ? { userId } : {}),\n ...(principal\n ? {\n principal: {\n issuer: principal.issuer,\n subject: principal.subject,\n principalType: principal.principalType,\n },\n tenantId: principal.tenantId,\n }\n : {}),\n ...(expiresAtMs !== undefined ? { expiresAtMs } : {}),\n },\n };\n if (new TextEncoder().encode(JSON.stringify(attachment)).byteLength > MAX_WS_ATTACHMENT_BYTES) {\n return false;\n }\n try {\n this.ctx.acceptWebSocket(ws, [roomTag(roomId), connTag(connId)]);\n ws.serializeAttachment(attachment);\n } catch {\n try {\n ws.close(1008, 'Connection rejected');\n } catch {\n // already closed\n }\n return false;\n }\n\n const client = this.registry.clientFor(ws);\n try {\n await this.dispatcher.handleOpen(path, client);\n // Another callback may have rejected the socket while the asynchronous\n // connection hook was still pending. Never resurrect that terminal state\n // after the hook resolves.\n if (!this.transition(ws, 'active', 'pending')) {\n throw new Error('Unable to persist authorized WebSocket state');\n }\n return true;\n } catch (err) {\n await this.dispatcher.handleError(path, client, err).catch(() => {});\n this.transition(ws, 'rejected');\n try {\n ws.close(1008, 'Connection rejected');\n } catch {\n // already closed\n }\n return false;\n }\n }\n\n async onMessage(ws: WsLike, message: string | ArrayBuffer): Promise<void> {\n if (!this.isActive(ws)) {\n this.reject(ws, 'Connection is not authorized');\n return;\n }\n let queue = this.#messages.get(ws);\n if (!queue) {\n const path = socketAttachment(ws)?.path;\n const options = this.dispatcher.collectEntrypoints().find((entry) => entry.meta.path === path)\n ?.meta.options;\n queue = new WsMessageQueue(\n () => this.reject(ws, 'WebSocket message budget exceeded', 1013),\n options?.maxPendingMessages,\n options?.maxPendingBytes,\n );\n this.#messages.set(ws, queue);\n }\n await queue.run(message, async () => {\n if (!this.isActive(ws)) return;\n const client = this.registry.clientFor(ws);\n await this.dispatcher.dispatchMessage(client.path, client, message);\n });\n }\n\n async onClose(ws: WsLike, code: number, reason: string): Promise<void> {\n this.#messages.get(ws)?.stop();\n if (!this.isActive(ws, false)) {\n this.transition(ws, 'rejected');\n try {\n ws.close();\n } catch {\n // already closed\n }\n return;\n }\n const client = this.registry.clientFor(ws);\n await this.dispatcher.handleClose(client.path, client, code, reason);\n // Complete the closing handshake. Only 1000 and 3000-4999 are valid for\n // close(); reserved/abnormal codes (1005/1006/1015, etc.) throw a RangeError,\n // so fall back to a codeless close on those.\n try {\n if (code === 1000 || (code >= 3000 && code <= 4999)) ws.close(code, reason);\n else ws.close();\n } catch {\n // socket already closed\n }\n }\n\n async onError(ws: WsLike, err: unknown): Promise<void> {\n if (!this.isActive(ws, false)) {\n this.reject(ws, 'Connection failed before authorization');\n return;\n }\n const client = this.registry.clientFor(ws);\n await this.dispatcher.handleError(client.path, client, err);\n }\n\n /** Deliver a broadcast command to this DO's local sockets (RPC entry point). */\n broadcast(cmd: BroadcastCommand): void | Promise<void> {\n assertBroadcastCommandFits(cmd, this.dispatcher.getMaximumGatewayFrameBytes());\n return this.registry.deliverLocal(cmd);\n }\n\n private transition(\n ws: WsLike,\n state: WsAttachment['state'],\n expectedState?: WsAttachment['state'],\n ): boolean {\n try {\n const attachment = socketAttachment(ws);\n if (!attachment) return false;\n if (expectedState !== undefined && attachment.state !== expectedState) return false;\n attachment.state = state;\n ws.serializeAttachment(attachment);\n return true;\n } catch {\n return false;\n }\n }\n\n private isActive(ws: WsLike, checkExpiry = true): boolean {\n try {\n const attachment = socketAttachment(ws);\n if (!attachment || attachment.state !== 'active') return false;\n return (\n !checkExpiry ||\n attachment.expiresAtMs === undefined ||\n (Number.isSafeInteger(attachment.expiresAtMs) && attachment.expiresAtMs > Date.now())\n );\n } catch {\n return false;\n }\n }\n\n private reject(ws: WsLike, reason: string, code = 1008): void {\n this.#messages.get(ws)?.stop();\n this.transition(ws, 'rejected');\n try {\n ws.close(code, reason);\n } catch {\n // already closed\n }\n }\n}\n","import { bootstrap, VelaApplication } from '@velajs/vela';\nimport type { InjectionToken, Type } from '@velajs/vela';\nimport {\n local,\n readWsEntrypointMeta,\n WsDispatcher,\n WsServerImpl,\n WS_SERVER,\n} from '@velajs/vela/websocket';\nimport type { WsServer } from '@velajs/vela/websocket';\nimport type { LiveEngine } from '@velajs/vela/live';\nimport { registerCloudflareEnvironment } from '../environment';\nimport { CfWsClient } from './cf-ws-client';\nimport { CfRoomRegistry } from './cf-room-registry';\nimport { initDoLive, initializeDoLiveResources } from './do-live';\nimport type { DoStateLike } from './do-state';\nimport { WsServerHolder } from './ws-server-holder';\n\nexport interface DoRuntime {\n dispatcher: WsDispatcher;\n registry: CfRoomRegistry;\n server: WsServer;\n /** Gateway paths from `app.entrypoints.ofKind('websocket')` (discovery order). */\n gatewayPaths: string[];\n /** The live-query engine (undefined when the app doesn't import LiveModule). */\n live?: LiveEngine;\n close(signal?: string): Promise<void>;\n}\n\n/**\n * Slim DI bootstrap for the Durable Object isolate: wires the container and runs\n * `OnModuleInit`/`OnApplicationBootstrap` (so `WsDispatcher` discovers gateways)\n * WITHOUT building the Hono app/routes the DO never serves. Cloudflare binding\n * refs are initialized straight from the DO's `env`, and the ctx-backed server\n * is bound before bootstrap lifecycle so gateway `afterInit`/handlers see it.\n */\nexport async function buildDoRuntime<T extends object>(\n rootModule: Type,\n ctx: DoStateLike,\n options: { env: T; envToken: InjectionToken<T> },\n): Promise<DoRuntime> {\n const { container, routeManager, loader } = await bootstrap(rootModule, {\n configureContainer: (container) => {\n registerCloudflareEnvironment(container, { token: options.envToken, env: options.env });\n },\n });\n\n const registry = new CfRoomRegistry(ctx);\n const driver = local();\n driver.bind(registry);\n const server = new WsServerImpl(driver);\n\n if (container.has(WS_SERVER)) {\n const holder = await container.resolveAsync(WS_SERVER);\n if (holder instanceof WsServerHolder) holder.setTarget(server);\n }\n\n const app = new VelaApplication(container, routeManager);\n app.setInstances(await loader.resolveAllInstances());\n initializeDoLiveResources(container, ctx);\n await app.callOnModuleInit();\n await app.callOnApplicationBootstrap();\n\n // The entrypoint registry is the transport contract: one 'websocket' entry\n // per discovered gateway ({ meta: { path, dispatcher } }). Built by\n // callOnApplicationBootstrap(), so this slim no-routes path has it too.\n const wsEntrypoints = app.entrypoints.ofKind('websocket', readWsEntrypointMeta);\n const dispatcher = wsEntrypoints[0]?.meta.dispatcher ?? app.get(WsDispatcher);\n registry.setSendPolicyResolver(\n (path) => wsEntrypoints.find((entry) => entry.meta.path === path)?.meta.options.sendPolicy,\n );\n registry.setFrameLimitResolver((path) => dispatcher.getGatewayMaxFrameBytes(path));\n registry.setDeliveryAuthorizer((client) => {\n const path = client instanceof CfWsClient ? client.path : '';\n return dispatcher.authorizeDelivery(path, client);\n });\n\n // Live queries: wire the SQLite cursor log + local driver mode and replay\n // hibernation-persisted subscriptions into the fresh engine.\n const live = initDoLive(app, ctx, registry);\n\n return {\n // Zero gateways still yields a live dispatcher (module imported, nothing\n // decorated) — fall back to resolving it directly.\n dispatcher,\n registry,\n server,\n gatewayPaths: wsEntrypoints.map((ep) => ep.meta.path),\n live,\n close: (signal?: string) => app.close(signal),\n };\n}\n","import { DurableObject } from 'cloudflare:workers';\nimport type { InjectionToken } from '@velajs/vela';\nimport type { BroadcastCommand } from '@velajs/vela/websocket';\nimport type {\n CommitStamp,\n InvalidationCommand,\n LiveEngine,\n LiveInspection,\n} from '@velajs/vela/live';\nimport { buildDoRuntime } from './do-bootstrap';\nimport { DoWebSocketHost, type WsConnectionPrincipal } from './do-websocket-host';\nimport { armDoPitr, readDoPitrBookmark } from './do-pitr';\nimport { resolveCloudflareRoot } from '../root-module';\nimport type { CloudflareRoot } from '../root-module';\nimport type {\n DoPitrArmOptions,\n DoPitrArmResult,\n DoPitrBookmarkRead,\n VelaDoPitrRpc,\n} from './do-pitr';\n\nconst PING = '{\"event\":\"$ping\"}';\nconst PONG = '{\"event\":\"$pong\"}';\nconst MAX_IDENTITY_FIELD_BYTES = 2048;\nconst encoder = new TextEncoder();\n\nfunction isIdentityField(value: string | null): value is string {\n return (\n value !== null &&\n value.length > 0 &&\n encoder.encode(value).byteLength <= MAX_IDENTITY_FIELD_BYTES\n );\n}\n\n/**\n * Base class for the WebSocket Durable Object. The user exports a named subclass\n * (matching their `wrangler.toml` `class_name`) built from their `AppModule`:\n *\n * ```ts\n * export class ChatRoom extends VelaWebSocketDurableObject(AppModule, { envToken: ENV }) {}\n * ```\n *\n * It owns the raw hibernation socket lifecycle (Hono's `upgradeWebSocket` cannot\n * bridge DO hibernation) and forwards every event into the runtime-agnostic\n * `WsDispatcher` via {@link DoWebSocketHost}.\n */\nexport function VelaWebSocketDurableObject<T extends object>(\n rootModule: CloudflareRoot<NoInfer<T>>,\n options: { envToken: InjectionToken<T> },\n): new (\n ctx: DurableObjectState,\n env: T,\n) => DurableObject<T> &\n VelaDoPitrRpc & {\n broadcast(cmd: BroadcastCommand): Promise<void>;\n invalidate(cmd: InvalidationCommand): Promise<CommitStamp | undefined>;\n inspectLive(): Promise<LiveInspection>;\n } {\n return class VelaWsDurableObject extends DurableObject<T> {\n private host!: DoWebSocketHost;\n private liveEngine?: LiveEngine;\n private readonly ready: Promise<void>;\n\n constructor(ctx: DurableObjectState, env: T) {\n super(ctx, env);\n // Application-level ping/pong answered WITHOUT waking a hibernated DO.\n try {\n ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(PING, PONG));\n } catch {\n // Older runtimes without auto-response — fine, protocol pings still work.\n }\n this.ready = ctx.blockConcurrencyWhile(async () => {\n const runtime = await buildDoRuntime(resolveCloudflareRoot(rootModule, env), ctx, {\n ...options,\n env,\n });\n this.host = new DoWebSocketHost(\n ctx,\n runtime.dispatcher,\n runtime.registry,\n runtime.gatewayPaths,\n );\n this.liveEngine = runtime.live;\n });\n }\n\n /** Intra-worker RPC only; HTTP fetch never exposes this admin snapshot. */\n async inspectLive(): Promise<LiveInspection> {\n await this.ready;\n return this.liveEngine?.inspect() ?? { subscriptions: [], rooms: [] };\n }\n\n override async fetch(request: Request): Promise<Response> {\n await this.ready;\n if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket') {\n return new Response('Expected WebSocket upgrade', { status: 426 });\n }\n\n const url = new URL(request.url);\n // Headers are primary (carry auth + multi-gateway routing); fall back to the\n // DO's own name (set via idFromName(room)) and the single gateway path so a\n // plain `stub.fetch(request)` forward still works.\n const roomId = request.headers.get('x-vela-room') ?? this.ctx.id.name ?? url.pathname;\n const path = request.headers.get('x-vela-path') ?? this.host.defaultPath() ?? url.pathname;\n const userId = request.headers.get('x-vela-user') || undefined;\n const rawExpiresAtMs = request.headers.get('x-vela-expires-at-ms');\n const parsedExpiresAtMs = rawExpiresAtMs === null ? undefined : Number(rawExpiresAtMs);\n if (\n parsedExpiresAtMs !== undefined &&\n (!Number.isSafeInteger(parsedExpiresAtMs) || parsedExpiresAtMs <= 0)\n ) {\n return new Response('Invalid WebSocket identity expiry', { status: 403 });\n }\n\n const issuer = request.headers.get('x-vela-issuer');\n const subject = request.headers.get('x-vela-subject');\n const principalType = request.headers.get('x-vela-principal-type');\n const tenantId = request.headers.get('x-vela-tenant');\n const hasPrincipalHeader =\n issuer !== null || subject !== null || principalType !== null || tenantId !== null;\n let principal: WsConnectionPrincipal | undefined;\n if (hasPrincipalHeader) {\n if (\n !isIdentityField(issuer) ||\n !isIdentityField(subject) ||\n (principalType !== 'user' && principalType !== 'service') ||\n !isIdentityField(tenantId) ||\n parsedExpiresAtMs === undefined ||\n (userId !== undefined && userId !== subject)\n ) {\n return new Response('Invalid WebSocket principal', { status: 403 });\n }\n principal = { issuer, subject, principalType, tenantId };\n } else if (parsedExpiresAtMs !== undefined) {\n return new Response('WebSocket identity tuple is missing', { status: 403 });\n }\n\n if (parsedExpiresAtMs !== undefined && parsedExpiresAtMs <= Date.now()) {\n return new Response('WebSocket identity expired', { status: 403 });\n }\n\n const { 0: client, 1: server } = new WebSocketPair();\n const accepted = await this.host.accept(\n server,\n path,\n roomId,\n userId,\n parsedExpiresAtMs,\n principal,\n );\n if (!accepted) return new Response('WebSocket connection rejected', { status: 403 });\n return new Response(null, { status: 101, webSocket: client });\n }\n\n override async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {\n await this.ready;\n await this.host.onMessage(ws, message);\n }\n\n override async webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void> {\n await this.ready;\n await this.host.onClose(ws, code, reason);\n }\n\n override async webSocketError(ws: WebSocket, error: unknown): Promise<void> {\n await this.ready;\n await this.host.onError(ws, error);\n }\n\n /** DO RPC — server-initiated broadcast forwarded from a Worker (see `broadcastToRoom`). */\n async broadcast(cmd: BroadcastCommand): Promise<void> {\n await this.ready;\n await this.host.broadcast(cmd);\n }\n\n /**\n * DO RPC — live tag invalidation forwarded from a Worker (durableObjectLive /\n * liveInvalidateToRoom). Appends to THIS log scope's cursor log and returns\n * the commit stamp (what `Vela-Commit-Cursor` carries); the subscription\n * refreshes fan out asynchronously.\n */\n async invalidate(cmd: InvalidationCommand): Promise<CommitStamp | undefined> {\n await this.ready;\n return this.liveEngine?.applyInvalidation(cmd);\n }\n\n // -- Durable Object PITR (point-in-time recovery) RPC ---------------------\n //\n // GUARD / reachability: these are admin-privileged operations (a restore can\n // roll the DO's SQLite state back up to 30 days and, with `restart`, abort\n // the DO to apply it now). They are safe to expose here because a Durable\n // Object's RPC methods are NOT network-reachable: a `DurableObjectStub` is\n // obtainable ONLY from a Worker that binds this DO namespace, and RPC calls\n // travel Cloudflare's internal capability channel — never the public\n // internet. A WebSocket/HTTP client reaches only `fetch()` above, never these\n // methods. The security boundary is therefore the WORKER-SIDE studio admin\n // gate that fronts the `CloudflareDoTimeTravelPort` (master admin token + the\n // 428 confirm challenge) — the identical trust model as `broadcast()` /\n // `invalidate()` on this same DO. See `do-pitr.ts` for the storage wrappers.\n\n /** DO RPC — the current PITR bookmark (typed-unavailable on a non-SQLite DO). */\n async pitrCurrentBookmark(): Promise<DoPitrBookmarkRead> {\n await this.ready;\n return readDoPitrBookmark(this.ctx.storage);\n }\n\n /** DO RPC — the PITR bookmark closest to `time` (+ the current bookmark). */\n async pitrBookmarkForTime(time: number | string): Promise<DoPitrBookmarkRead> {\n await this.ready;\n return readDoPitrBookmark(this.ctx.storage, time);\n }\n\n /**\n * DO RPC — arm a PITR restore and return the undo bookmark. When `restart` is\n * requested, `ctx.abort()` is called AFTER the undo bookmark is computed so\n * the recovery applies on the immediately-following session; otherwise the\n * restore applies on the next natural restart (`restarted: false`).\n */\n async pitrArmRestore(opts: DoPitrArmOptions): Promise<DoPitrArmResult> {\n await this.ready;\n const result = await armDoPitr(this.ctx.storage, opts);\n if (opts.restart === true) this.ctx.abort('vela PITR restore');\n return result;\n }\n };\n}\n","import { DurableObject } from 'cloudflare:workers';\nimport { MAX_NONCE_BYTES, isCanonicalBoundedText, isValidExpiry } from './nonce-validation';\nconst EXPIRED_DELETE_BATCH = 1_024;\n\nconst CREATE_TABLE =\n 'CREATE TABLE IF NOT EXISTS __vela_nonce_claims (' +\n 'nonce TEXT PRIMARY KEY NOT NULL, ' +\n 'expires_at INTEGER NOT NULL CHECK (expires_at > 0)' +\n ') WITHOUT ROWID';\nconst CREATE_EXPIRY_INDEX =\n 'CREATE INDEX IF NOT EXISTS __vela_nonce_claims_expiry ON __vela_nonce_claims (expires_at)';\nconst DELETE_EXPIRED =\n 'DELETE FROM __vela_nonce_claims WHERE nonce IN (' +\n 'SELECT nonce FROM __vela_nonce_claims ' +\n 'WHERE expires_at < ? ORDER BY expires_at LIMIT ?' +\n ')';\nconst INSERT_CLAIM =\n 'INSERT INTO __vela_nonce_claims (nonce, expires_at) VALUES (?, ?) ' +\n 'ON CONFLICT(nonce) DO NOTHING RETURNING nonce, expires_at';\n\ninterface NonceSqlCursor {\n toArray(): Record<string, unknown>[];\n}\n\ninterface NonceSqlStorage {\n exec(query: string, ...bindings: unknown[]): NonceSqlCursor;\n}\n\n/**\n * SQLite Durable Object that atomically consumes nonces.\n *\n * Export this class from the Worker entry and register it through a Wrangler\n * `new_sqlite_classes` migration. `INSERT ... ON CONFLICT DO NOTHING RETURNING`\n * is the single-use decision; all SQL runs synchronously before the RPC method\n * yields, and the nonce primary key is the final concurrency boundary.\n */\nexport class VelaNonceDurableObject extends DurableObject<Record<string, unknown>> {\n private sql?: NonceSqlStorage;\n\n constructor(ctx: DurableObjectState, env: Record<string, unknown>) {\n super(ctx, env);\n try {\n const sql = ctx.storage?.sql;\n if (!sql || typeof sql.exec !== 'function') return;\n sql.exec(CREATE_TABLE);\n sql.exec(CREATE_EXPIRY_INDEX);\n this.sql = sql;\n } catch {\n // A non-SQLite/misconfigured class stays fail-closed: every claim denies.\n this.sql = undefined;\n }\n }\n\n async claim(nonce: string, expEpochSeconds: number): Promise<boolean> {\n const sql = this.sql;\n if (!sql) return false;\n\n const now = Math.floor(Date.now() / 1_000);\n if (!isCanonicalBoundedText(nonce, MAX_NONCE_BYTES) || !isValidExpiry(expEpochSeconds, now)) {\n return false;\n }\n\n try {\n // Keep cleanup bounded so one claim cannot monopolize the DO. Entries at\n // exactly `now` remain: Vela's invocation verifier accepts `now === exp`.\n sql.exec(DELETE_EXPIRED, now, EXPIRED_DELETE_BATCH);\n\n const cursor = sql.exec(INSERT_CLAIM, nonce, expEpochSeconds);\n if (!cursor || typeof cursor.toArray !== 'function') return false;\n const rows: unknown = cursor.toArray();\n if (!Array.isArray(rows) || rows.length !== 1) return false;\n\n const row: unknown = rows[0];\n if (typeof row !== 'object' || row === null) return false;\n if (\n !Object.hasOwn(row, 'nonce') ||\n !Object.hasOwn(row, 'expires_at') ||\n !('nonce' in row) ||\n !('expires_at' in row)\n ) {\n return false;\n }\n return row.nonce === nonce && row.expires_at === expEpochSeconds;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as DoPitrId, c as DoPitrUnavailableError, d as isDoPitrUnavailable, f as readDoPitrBookmark, i as DoPitrBookmarkRead, l as VelaDoPitrRpc, n as DoPitrArmOptions, o as DoPitrNamespace, p as CloudflareRoot, r as DoPitrArmResult, s as DoPitrStorage, t as VelaNonceDurableObject, u as armDoPitr } from "./nonce.durable-object-Df3_42Sy.js";
|
|
2
2
|
import { AsyncCacheStore, DynamicModule, InjectionToken, NonceStore, RuntimeAdapter, ThrottlerStore, Type, VelaApplication, VelaMiddlewareHandler, VelaSecurityOptions } from "@velajs/vela";
|
|
3
|
-
import { BroadcastCommand, ConnectedSocket, MessageBody, OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, SubscribeMessage, WebSocketGateway, WebSocketGatewayOptions, WebSocketServer, WsClient, WsDispatcher, WsException, WsMessage, WsResponse, WsServer } from "@velajs/vela/websocket";
|
|
3
|
+
import { BroadcastCommand, ConnectedSocket, MessageBody, OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, SubscribeMessage, WebSocketGateway, WebSocketGatewayOptions, WebSocketSendGate, WebSocketServer, WsClient, WsDispatcher, WsException, WsMessage, WsResponse, WsServer } from "@velajs/vela/websocket";
|
|
4
4
|
import { DownloadResult, PresignMethod, PresignedUrlResult, StorageBody, StorageDriver, UploadOptions, UploadResult } from "@velajs/vela/storage";
|
|
5
5
|
import { CommitStamp, CursorLog, InvalidationCommand, LiveDriver, LiveEngine, LiveInvalidationSink, ResumeVerdict } from "@velajs/vela/live";
|
|
6
6
|
import { Context, ExecutionContext } from "hono";
|
|
@@ -55,9 +55,8 @@ type MountOpenApiOptions = Parameters<VelaApplication['mountOpenApi']>[0];
|
|
|
55
55
|
* ```
|
|
56
56
|
*/
|
|
57
57
|
export declare class CloudflareApplication<T extends object = object> {
|
|
58
|
-
private
|
|
58
|
+
#private;
|
|
59
59
|
readonly env: T;
|
|
60
|
-
private wsGatewayRoutes;
|
|
61
60
|
constructor(app: VelaApplication, env: T);
|
|
62
61
|
readonly fetch: (request: Request, env: T, ctx?: ExecutionContext) => Promise<Response>;
|
|
63
62
|
getHonoApp(): ReturnType<VelaApplication['getHonoApp']>;
|
|
@@ -97,13 +96,12 @@ export declare class CloudflareApplication<T extends object = object> {
|
|
|
97
96
|
*/
|
|
98
97
|
mountOpenApi(options: MountOpenApiOptions): this;
|
|
99
98
|
/**
|
|
100
|
-
* @internal
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* kinds) at dispatch time.
|
|
99
|
+
* @internal Upgrade routes come from validated gateway entrypoints, including
|
|
100
|
+
* request-scoped gateways without a bootstrap instance. Retain the instance
|
|
101
|
+
* scan for legacy applications that only declare forwarding metadata.
|
|
104
102
|
*/
|
|
105
103
|
scanInstances(instances: unknown[]): void;
|
|
106
|
-
/** @internal — upgrade routes discovered from
|
|
104
|
+
/** @internal — upgrade routes discovered from the application's gateways. */
|
|
107
105
|
getWsGatewayRoutes(): WsGatewayRoute[];
|
|
108
106
|
/**
|
|
109
107
|
* Handle Cloudflare scheduled (cron) events.
|
|
@@ -414,6 +412,21 @@ interface ScheduledMetadata {
|
|
|
414
412
|
cron: string;
|
|
415
413
|
methodName: string;
|
|
416
414
|
}
|
|
415
|
+
/** Compatible with the existing programmatic scheduled() entrypoint. */
|
|
416
|
+
interface ScheduledEvent {
|
|
417
|
+
readonly cron: string;
|
|
418
|
+
readonly scheduledTime?: number;
|
|
419
|
+
}
|
|
420
|
+
/** Native controller passed unchanged to a Worker handler. Call noRetry on its receiver. */
|
|
421
|
+
interface ScheduledController extends ScheduledEvent {
|
|
422
|
+
readonly scheduledTime: number;
|
|
423
|
+
noRetry(): void;
|
|
424
|
+
}
|
|
425
|
+
interface ScheduledContext {
|
|
426
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
427
|
+
}
|
|
428
|
+
type ScheduledHandler<Env extends object = object> = (controller: ScheduledController, env: Env, context: ScheduledContext) => void | Promise<void>;
|
|
429
|
+
export declare function parseScheduledMetadata(value: unknown): ScheduledMetadata;
|
|
417
430
|
/**
|
|
418
431
|
* Marks a method as a scheduled (cron) handler.
|
|
419
432
|
*
|
|
@@ -618,5 +631,5 @@ interface DurableObjectNonceStoreOptions {
|
|
|
618
631
|
*/
|
|
619
632
|
export declare function durableObjectNonceStore(options: DurableObjectNonceStoreOptions): NonceStore;
|
|
620
633
|
//#endregion
|
|
621
|
-
export { type BroadcastNamespace, type CfLiveDriver, type CloudflareRateLimitBinding, type CloudflareRateLimitStoreOptions, type CloudflareRoot, type CloudflareWorkerOptions, ConnectedSocket, type CreateCloudflareAppOptions, type DiskConfig, type DoPitrArmOptions, type DoPitrArmResult, type DoPitrBookmarkRead, type DoPitrId, type DoPitrNamespace, type DoPitrStorage, DoPitrUnavailableError, type DurableObjectLiveOptions, type DurableObjectNonceNamespace, type DurableObjectNonceStoreOptions, type FlagshipBinding, type FlagshipFlagDriverOptions, type KvFlagDriverOptions, type LiveNamespace, MessageBody, type MountOpenApiOptions, type OnGatewayConnection, type OnGatewayDisconnect, type OnGatewayInit, type PresignedUrlConfig, type QueueConsumerMetadata, type ScheduledMetadata, type StorageModuleOptions, SubscribeMessage, type VelaDoPitrRpc, WebSocketGateway, WebSocketServer, type WsClient, WsException, type WsGatewayRoute, type WsMessage, type WsResponse, type WsServer, armDoPitr, isDoPitrUnavailable, readDoPitrBookmark };
|
|
634
|
+
export { type BroadcastNamespace, type CfLiveDriver, type CloudflareRateLimitBinding, type CloudflareRateLimitStoreOptions, type CloudflareRoot, type CloudflareWorkerOptions, ConnectedSocket, type CreateCloudflareAppOptions, type DiskConfig, type DoPitrArmOptions, type DoPitrArmResult, type DoPitrBookmarkRead, type DoPitrId, type DoPitrNamespace, type DoPitrStorage, DoPitrUnavailableError, type DurableObjectLiveOptions, type DurableObjectNonceNamespace, type DurableObjectNonceStoreOptions, type FlagshipBinding, type FlagshipFlagDriverOptions, type KvFlagDriverOptions, type LiveNamespace, MessageBody, type MountOpenApiOptions, type OnGatewayConnection, type OnGatewayDisconnect, type OnGatewayInit, type PresignedUrlConfig, type QueueConsumerMetadata, type ScheduledContext, type ScheduledController, type ScheduledEvent, type ScheduledHandler, type ScheduledMetadata, type StorageModuleOptions, SubscribeMessage, type VelaDoPitrRpc, WebSocketGateway, WebSocketServer, type WsClient, WsException, type WsGatewayRoute, type WsMessage, type WsResponse, type WsServer, armDoPitr, isDoPitrUnavailable, readDoPitrBookmark };
|
|
622
635
|
//# sourceMappingURL=index.d.ts.map
|