@velajs/cloudflare 2.0.0 → 3.0.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 +119 -0
- package/README.md +62 -24
- package/dist/durable-objects.d.ts +2 -2
- package/dist/durable-objects.js +41 -22
- package/dist/durable-objects.js.map +1 -1
- package/dist/index.d.ts +46 -19
- package/dist/index.js +178 -71
- package/dist/index.js.map +1 -1
- package/dist/{nonce-validation-LE0vFk-7.js → nonce-validation-CeVihShV.js} +113 -41
- package/dist/nonce-validation-CeVihShV.js.map +1 -0
- package/dist/{nonce.durable-object-Df3_42Sy.d.ts → nonce.durable-object-DffHMRnU.d.ts} +4 -4
- package/dist/queue.d.ts +32 -0
- package/dist/queue.js +78 -0
- package/dist/queue.js.map +1 -0
- package/package.json +15 -11
- package/dist/nonce-validation-LE0vFk-7.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
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";
|
|
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-DffHMRnU.js";
|
|
2
|
+
import { AsyncCacheStore, CacheEntry, CacheEntryReader, CacheEntryWriter, CacheInvalidationStore, DynamicModule, InjectionToken, NonceStore, RuntimeAdapter, ThrottlerStore, Type, VelaApplication, VelaMiddlewareHandler, VelaSecurityOptions } from "@velajs/vela";
|
|
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.
|
|
@@ -273,22 +271,36 @@ export declare const STORAGE_OPTIONS: InjectionToken<StorageModuleOptions>;
|
|
|
273
271
|
//#endregion
|
|
274
272
|
//#region src/services/kv-cache.store.d.ts
|
|
275
273
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* Reads return unknown JSON; validate values at the consuming boundary.
|
|
280
|
-
*
|
|
281
|
-
* Note: Cloudflare KV requires `expirationTtl >= 60s`, so sub-minute TTLs are
|
|
282
|
-
* clamped up. Keep short TTLs on the memory tier; use KV for longer-lived entries.
|
|
274
|
+
* Native KV JSON value store. Metadata retains logical expiry even when KV's
|
|
275
|
+
* physical retention rounds up to its 60-second minimum. Legacy values without
|
|
276
|
+
* metadata remain readable, but cannot safely backfill another tier.
|
|
283
277
|
*/
|
|
284
|
-
export declare class KVCacheStore implements AsyncCacheStore {
|
|
278
|
+
export declare class KVCacheStore implements AsyncCacheStore, CacheEntryReader, CacheEntryWriter {
|
|
285
279
|
private readonly ns;
|
|
286
280
|
constructor(ns: KVNamespace);
|
|
287
281
|
get(key: string): Promise<unknown>;
|
|
282
|
+
getEntry(key: string): Promise<{
|
|
283
|
+
value: unknown;
|
|
284
|
+
expiresAt?: number;
|
|
285
|
+
} | undefined>;
|
|
288
286
|
set(key: string, value: unknown, ttl?: number): Promise<void>;
|
|
287
|
+
setEntry(key: string, entry: CacheEntry): Promise<void>;
|
|
289
288
|
del(key: string): Promise<void>;
|
|
289
|
+
/** Namespace-wide, best effort. Use a dedicated value namespace; never use for scoped invalidation. */
|
|
290
290
|
clear(): Promise<void>;
|
|
291
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Optional, eventually consistent generation store. Use a dedicated KV namespace
|
|
294
|
+
* without TTL/lifecycle cleanup. Never delete/reset generations while entries can
|
|
295
|
+
* survive. Concurrent writes and cached/negative reads prevent strong invalidation;
|
|
296
|
+
* this is unsuitable for strict read-after-write or authorization revocation.
|
|
297
|
+
*/
|
|
298
|
+
export declare class KVCacheInvalidationStore implements CacheInvalidationStore {
|
|
299
|
+
private readonly ns;
|
|
300
|
+
constructor(ns: KVNamespace);
|
|
301
|
+
getVersion(key: string): Promise<string>;
|
|
302
|
+
invalidate(key: string): Promise<void>;
|
|
303
|
+
}
|
|
292
304
|
//#endregion
|
|
293
305
|
//#region src/services/flagship-flag.driver.d.ts
|
|
294
306
|
/**
|
|
@@ -414,6 +426,21 @@ interface ScheduledMetadata {
|
|
|
414
426
|
cron: string;
|
|
415
427
|
methodName: string;
|
|
416
428
|
}
|
|
429
|
+
/** Compatible with the existing programmatic scheduled() entrypoint. */
|
|
430
|
+
interface ScheduledEvent {
|
|
431
|
+
readonly cron: string;
|
|
432
|
+
readonly scheduledTime?: number;
|
|
433
|
+
}
|
|
434
|
+
/** Native controller passed unchanged to a Worker handler. Call noRetry on its receiver. */
|
|
435
|
+
interface ScheduledController extends ScheduledEvent {
|
|
436
|
+
readonly scheduledTime: number;
|
|
437
|
+
noRetry(): void;
|
|
438
|
+
}
|
|
439
|
+
interface ScheduledContext {
|
|
440
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
441
|
+
}
|
|
442
|
+
type ScheduledHandler<Env extends object = object> = (controller: ScheduledController, env: Env, context: ScheduledContext) => void | Promise<void>;
|
|
443
|
+
export declare function parseScheduledMetadata(value: unknown): ScheduledMetadata;
|
|
417
444
|
/**
|
|
418
445
|
* Marks a method as a scheduled (cron) handler.
|
|
419
446
|
*
|
|
@@ -618,5 +645,5 @@ interface DurableObjectNonceStoreOptions {
|
|
|
618
645
|
*/
|
|
619
646
|
export declare function durableObjectNonceStore(options: DurableObjectNonceStoreOptions): NonceStore;
|
|
620
647
|
//#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 };
|
|
648
|
+
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
649
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as durableObjectRoomName, T as roomToDurableId, _ as readDoPitrBookmark, a as durableObjectLive, b as assertCloudflareEnvironment, c as liveInvalidateToRoom, g as isDoPitrUnavailable, h as armDoPitr, i as durableObjectCursorLog, m as DoPitrUnavailableError, n as isValidExpiry, p as WsServerHolder, r as DoCursorLog, t as isCanonicalBoundedText, v as __decorate, x as registerCloudflareEnvironment, y as resolveCloudflareRoot } from "./nonce-validation-CeVihShV.js";
|
|
2
2
|
import { getConnInfo } from "hono/cloudflare-workers";
|
|
3
|
-
import { CRON_METADATA, ConfigurableModuleBuilder, Controller, Get, Inject, Injectable, InjectionToken, Module, PipelineRunner, Req, VelaFactory, buildEntrypointExecutionContext, createParamDecorator, defineMetadata, defineProvider, getMetadata, getTrustedRequestIdentity, registerEntrypointKind, runInEntrypointScope, shouldFilterCatch } from "@velajs/vela";
|
|
4
|
-
import {
|
|
5
|
-
import { ConnectedSocket, DEFAULT_WS_MAX_FRAME_BYTES, MessageBody, SubscribeMessage, WS_GATEWAY_METADATA, WS_SERVER, WebSocketGateway, WebSocketServer, WsDispatcher, WsException, assertBroadcastCommandFits, authenticateWebSocketUpgrade, resolveGatewayRoomId, resolveGatewayRoomParam, resolveMaxFrameBytes } from "@velajs/vela/websocket";
|
|
3
|
+
import { CRON_METADATA, ConfigurableModuleBuilder, Controller, Get, Inject, Injectable, InjectionToken, Module, PipelineRunner, Req, VelaFactory, buildEntrypointExecutionContext, createParamDecorator, defineMetadata, defineProvider, getEntrypointModuleId, getMetadata, getTrustedRequestIdentity, parseCron, registerEntrypointKind, resolveEntrypoint, resolveErrorReporter, resolveScopedComponentsAsync, runInEntrypointScope, shouldFilterCatch } from "@velajs/vela";
|
|
4
|
+
import { ConnectedSocket, DEFAULT_WS_MAX_FRAME_BYTES, MessageBody, SubscribeMessage, WS_GATEWAY_METADATA, WS_SERVER, WebSocketGateway, WebSocketServer, WsDispatcher, WsException, assertBroadcastCommandFits, authenticateWebSocketUpgrade, readWsEntrypointMeta, resolveGatewayRoomId, resolveGatewayRoomParam, resolveMaxFrameBytes } from "@velajs/vela/websocket";
|
|
6
5
|
import { STORAGE_SIGNED_URL_PURPOSE, joinStoragePath, signUrl, verifySignedUrl } from "@velajs/vela/storage";
|
|
7
6
|
//#region src/websocket/websocket-routing.ts
|
|
8
7
|
const MAX_IDENTITY_FIELD_BYTES = 2048;
|
|
@@ -123,7 +122,7 @@ registerEntrypointKind({
|
|
|
123
122
|
});
|
|
124
123
|
function invoke(instance, methodName, args) {
|
|
125
124
|
const method = Reflect.get(instance, methodName);
|
|
126
|
-
if (typeof method !== "function") throw new Error(`Method '${methodName}' is not a function on ${instance.constructor.name}`);
|
|
125
|
+
if (typeof method !== "function") throw new Error(`Method '${String(methodName)}' is not a function on ${instance.constructor.name}`);
|
|
127
126
|
return Reflect.apply(method, instance, args);
|
|
128
127
|
}
|
|
129
128
|
function entrypointString(meta, property) {
|
|
@@ -132,6 +131,12 @@ function entrypointString(meta, property) {
|
|
|
132
131
|
if (typeof value !== "string") throw new Error(`Invalid entrypoint metadata: ${property} must be a string.`);
|
|
133
132
|
return value;
|
|
134
133
|
}
|
|
134
|
+
/** Wait for every matching handler, even when one fails before its siblings. */
|
|
135
|
+
async function settleEntrypoints(work) {
|
|
136
|
+
const errors = (await Promise.allSettled(work)).flatMap((outcome) => outcome.status === "rejected" ? [outcome.reason] : []);
|
|
137
|
+
if (errors.length === 1) throw errors[0];
|
|
138
|
+
if (errors.length > 1) throw new AggregateError(errors, "Multiple entrypoint handlers failed.");
|
|
139
|
+
}
|
|
135
140
|
/**
|
|
136
141
|
* Wraps VelaApplication with Cloudflare-specific handlers:
|
|
137
142
|
* - `fetch` — HTTP request handler (from Hono)
|
|
@@ -162,20 +167,20 @@ function entrypointString(meta, property) {
|
|
|
162
167
|
* ```
|
|
163
168
|
*/
|
|
164
169
|
var CloudflareApplication = class {
|
|
165
|
-
app;
|
|
166
170
|
env;
|
|
167
|
-
wsGatewayRoutes = [];
|
|
171
|
+
#wsGatewayRoutes = [];
|
|
172
|
+
#app;
|
|
168
173
|
constructor(app, env) {
|
|
169
|
-
this.app = app;
|
|
170
174
|
this.env = env;
|
|
175
|
+
this.#app = app;
|
|
171
176
|
this.get = app.get.bind(app);
|
|
172
177
|
}
|
|
173
178
|
fetch = async (request, env, ctx) => {
|
|
174
179
|
assertCloudflareEnvironment(this.env, env);
|
|
175
|
-
return this
|
|
180
|
+
return this.#app.fetch(request, env, ctx);
|
|
176
181
|
};
|
|
177
182
|
getHonoApp() {
|
|
178
|
-
return this
|
|
183
|
+
return this.#app.getHonoApp();
|
|
179
184
|
}
|
|
180
185
|
/**
|
|
181
186
|
* Resolve a provider from the application's DI container (delegates to
|
|
@@ -191,7 +196,7 @@ var CloudflareApplication = class {
|
|
|
191
196
|
*/
|
|
192
197
|
get;
|
|
193
198
|
get entrypoints() {
|
|
194
|
-
return this
|
|
199
|
+
return this.#app.entrypoints;
|
|
195
200
|
}
|
|
196
201
|
/**
|
|
197
202
|
* Serve a pre-built OpenAPI document (and optionally a Scalar UI) on the
|
|
@@ -214,24 +219,34 @@ var CloudflareApplication = class {
|
|
|
214
219
|
* ```
|
|
215
220
|
*/
|
|
216
221
|
mountOpenApi(options) {
|
|
217
|
-
this
|
|
222
|
+
this.#app.mountOpenApi(options);
|
|
218
223
|
return this;
|
|
219
224
|
}
|
|
220
225
|
/**
|
|
221
|
-
* @internal
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* kinds) at dispatch time.
|
|
226
|
+
* @internal Upgrade routes come from validated gateway entrypoints, including
|
|
227
|
+
* request-scoped gateways without a bootstrap instance. Retain the instance
|
|
228
|
+
* scan for legacy applications that only declare forwarding metadata.
|
|
225
229
|
*/
|
|
226
230
|
scanInstances(instances) {
|
|
231
|
+
const routes = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const ep of this.#app.entrypoints.ofKind("websocket")) {
|
|
233
|
+
if (typeof ep.meta !== "object" || ep.meta === null || !("dispatcher" in ep.meta)) continue;
|
|
234
|
+
const meta = readWsEntrypointMeta(ep.meta);
|
|
235
|
+
if (meta.options.binding) routes.set(meta.path, {
|
|
236
|
+
path: meta.path,
|
|
237
|
+
binding: meta.options.binding,
|
|
238
|
+
options: { ...meta.options }
|
|
239
|
+
});
|
|
240
|
+
}
|
|
227
241
|
for (const instance of instances) {
|
|
228
242
|
if (!instance || typeof instance !== "object") continue;
|
|
229
|
-
|
|
243
|
+
for (const route of collectWsGatewayRoutes(instance)) if (!routes.has(route.path)) routes.set(route.path, route);
|
|
230
244
|
}
|
|
245
|
+
this.#wsGatewayRoutes.splice(0, this.#wsGatewayRoutes.length, ...routes.values());
|
|
231
246
|
}
|
|
232
|
-
/** @internal — upgrade routes discovered from
|
|
247
|
+
/** @internal — upgrade routes discovered from the application's gateways. */
|
|
233
248
|
getWsGatewayRoutes() {
|
|
234
|
-
return this
|
|
249
|
+
return [...this.#wsGatewayRoutes];
|
|
235
250
|
}
|
|
236
251
|
/**
|
|
237
252
|
* Handle Cloudflare scheduled (cron) events.
|
|
@@ -241,18 +256,13 @@ var CloudflareApplication = class {
|
|
|
241
256
|
*/
|
|
242
257
|
async scheduled(event, env, ctx) {
|
|
243
258
|
assertCloudflareEnvironment(this.env, env);
|
|
244
|
-
|
|
259
|
+
await settleEntrypoints([...this.#app.entrypoints.ofKind("cf:scheduled").map((ep) => ({
|
|
245
260
|
ep,
|
|
246
261
|
cron: entrypointString(ep.meta, "cron")
|
|
247
|
-
})), ...this
|
|
262
|
+
})), ...this.#app.entrypoints.ofKind("cf:vela-cron").map((ep) => ({
|
|
248
263
|
ep,
|
|
249
264
|
cron: entrypointString(ep.meta, "expression")
|
|
250
|
-
}))].filter((h) => h.cron === event.cron);
|
|
251
|
-
await Promise.all(handlers.map(({ ep }) => this.dispatchEntrypoint(ep, [
|
|
252
|
-
event,
|
|
253
|
-
env,
|
|
254
|
-
ctx
|
|
255
|
-
])));
|
|
265
|
+
}))].filter((h) => h.cron === event.cron).map(({ ep }) => this.dispatchEntrypoint(ep, event, env, ctx)));
|
|
256
266
|
}
|
|
257
267
|
/**
|
|
258
268
|
* Run one entrypoint handler inside a fresh request scope, through the
|
|
@@ -262,33 +272,61 @@ var CloudflareApplication = class {
|
|
|
262
272
|
* guard has no business rejecting a queue batch. Unclaimed errors rethrow
|
|
263
273
|
* so the platform's retry semantics stay intact.
|
|
264
274
|
*/
|
|
265
|
-
async dispatchEntrypoint(ep,
|
|
275
|
+
async dispatchEntrypoint(ep, payload, env, platformContext) {
|
|
266
276
|
const targetClass = ep.token;
|
|
267
277
|
if (typeof targetClass !== "function") throw new Error("Entrypoint token must be a class.");
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
await
|
|
287
|
-
|
|
278
|
+
if (ep.methodName === void 0) throw new Error("Entrypoint must declare a handler method.");
|
|
279
|
+
const methodName = ep.methodName;
|
|
280
|
+
const reportContext = {
|
|
281
|
+
edge: ep.kind.startsWith("cf:queue") ? "queue" : "schedule",
|
|
282
|
+
source: `${targetClass.name}.${String(methodName)}`
|
|
283
|
+
};
|
|
284
|
+
let reported;
|
|
285
|
+
try {
|
|
286
|
+
await runInEntrypointScope(this.#app.getContainer(), async (scope, lifetime) => {
|
|
287
|
+
const moduleId = getEntrypointModuleId(scope, ep);
|
|
288
|
+
const context = buildEntrypointExecutionContext(ep.kind, targetClass, methodName, payload, moduleId, scope);
|
|
289
|
+
const invocationContext = { waitUntil(promise) {
|
|
290
|
+
lifetime.waitUntil(promise);
|
|
291
|
+
platformContext.waitUntil(promise);
|
|
292
|
+
} };
|
|
293
|
+
let filters = [];
|
|
294
|
+
try {
|
|
295
|
+
filters = (await resolveScopedComponentsAsync("filter", targetClass, methodName, scope, moduleId)).toReversed();
|
|
296
|
+
const guards = await resolveScopedComponentsAsync("guard", targetClass, methodName, scope, moduleId);
|
|
297
|
+
const interceptors = await resolveScopedComponentsAsync("interceptor", targetClass, methodName, scope, moduleId);
|
|
298
|
+
await PipelineRunner.run({
|
|
299
|
+
context,
|
|
300
|
+
guards,
|
|
301
|
+
interceptors,
|
|
302
|
+
resolveArgs: async () => [
|
|
303
|
+
payload,
|
|
304
|
+
env,
|
|
305
|
+
invocationContext
|
|
306
|
+
],
|
|
307
|
+
invoke: async (args) => {
|
|
308
|
+
const instance = await resolveEntrypoint(scope, ep);
|
|
309
|
+
if (typeof instance !== "object" || instance === null) throw new Error("Entrypoint must resolve to an object.");
|
|
310
|
+
return invoke(instance, methodName, args);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
} catch (error) {
|
|
314
|
+
resolveErrorReporter(scope).report(error, reportContext);
|
|
315
|
+
for (const filter of filters) if (shouldFilterCatch(filter, error)) {
|
|
316
|
+
await filter.catch(error, context);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
reported = { error };
|
|
320
|
+
throw error;
|
|
288
321
|
}
|
|
289
|
-
|
|
322
|
+
});
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (!reported || reported.error !== error) {
|
|
325
|
+
const completionError = reported && error instanceof AggregateError && error.errors[0] === reported.error ? error.errors[1] : error;
|
|
326
|
+
resolveErrorReporter(this.#app.getContainer()).report(completionError, reportContext);
|
|
290
327
|
}
|
|
291
|
-
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
292
330
|
}
|
|
293
331
|
/**
|
|
294
332
|
* Handle Cloudflare Queue consumer events.
|
|
@@ -298,15 +336,12 @@ var CloudflareApplication = class {
|
|
|
298
336
|
*/
|
|
299
337
|
async queue(batch, env, ctx) {
|
|
300
338
|
assertCloudflareEnvironment(this.env, env);
|
|
301
|
-
const handlers = this.app.entrypoints.ofKind("cf:queue").filter((ep) => entrypointString(ep.meta, "queueName") === batch.queue);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
env,
|
|
305
|
-
ctx
|
|
306
|
-
])));
|
|
339
|
+
const handlers = [...this.#app.entrypoints.ofKind("cf:queue"), ...this.#app.entrypoints.ofKind("cf:queue:module")].filter((ep) => entrypointString(ep.meta, "queueName") === batch.queue);
|
|
340
|
+
if (handlers.length === 0) throw new Error(`No consumer for queue '${batch.queue}'.`);
|
|
341
|
+
await settleEntrypoints(handlers.map((ep) => this.dispatchEntrypoint(ep, batch, env, ctx)));
|
|
307
342
|
}
|
|
308
343
|
async close(signal) {
|
|
309
|
-
return this
|
|
344
|
+
return this.#app.close(signal);
|
|
310
345
|
}
|
|
311
346
|
};
|
|
312
347
|
//#endregion
|
|
@@ -331,13 +366,27 @@ function cloudflareAdapter(options) {
|
|
|
331
366
|
}
|
|
332
367
|
/** Build an application for one native Workers environment. Call inside a platform event. */
|
|
333
368
|
async function createCloudflareApp(rootModule, options) {
|
|
334
|
-
const velaApp = await VelaFactory.create(resolveCloudflareRoot(rootModule, options.env), {
|
|
369
|
+
const velaApp = await VelaFactory.create(await resolveCloudflareRoot(rootModule, options.env), {
|
|
335
370
|
globalPrefix: options.globalPrefix,
|
|
336
371
|
security: options.security,
|
|
337
372
|
middleware: options.middleware?.(options.env),
|
|
338
373
|
adapters: [cloudflareAdapter(options)]
|
|
339
374
|
});
|
|
340
375
|
const app = new CloudflareApplication(velaApp, options.env);
|
|
376
|
+
const consumers = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const entry of [...app.entrypoints.ofKind("cf:queue"), ...app.entrypoints.ofKind("cf:queue:module")]) {
|
|
378
|
+
const meta = entry.meta;
|
|
379
|
+
if (typeof meta !== "object" || meta === null || !("queueName" in meta) || typeof meta.queueName !== "string") {
|
|
380
|
+
await app.close();
|
|
381
|
+
throw new TypeError("Invalid queue consumer metadata.");
|
|
382
|
+
}
|
|
383
|
+
const previous = consumers.get(meta.queueName);
|
|
384
|
+
if (previous && (previous === "cf:queue:module" || entry.kind === "cf:queue:module")) {
|
|
385
|
+
await app.close();
|
|
386
|
+
throw new Error(`Ambiguous consumer ownership for queue '${meta.queueName}'.`);
|
|
387
|
+
}
|
|
388
|
+
consumers.set(meta.queueName, entry.kind);
|
|
389
|
+
}
|
|
341
390
|
app.scanInstances(velaApp.getInstances());
|
|
342
391
|
registerWebSocketRoutes(app.getHonoApp(), app.getWsGatewayRoutes());
|
|
343
392
|
return app;
|
|
@@ -675,13 +724,9 @@ StorageModule = __decorate([Module({
|
|
|
675
724
|
//#endregion
|
|
676
725
|
//#region src/services/kv-cache.store.ts
|
|
677
726
|
/**
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
* Reads return unknown JSON; validate values at the consuming boundary.
|
|
682
|
-
*
|
|
683
|
-
* Note: Cloudflare KV requires `expirationTtl >= 60s`, so sub-minute TTLs are
|
|
684
|
-
* clamped up. Keep short TTLs on the memory tier; use KV for longer-lived entries.
|
|
727
|
+
* Native KV JSON value store. Metadata retains logical expiry even when KV's
|
|
728
|
+
* physical retention rounds up to its 60-second minimum. Legacy values without
|
|
729
|
+
* metadata remain readable, but cannot safely backfill another tier.
|
|
685
730
|
*/
|
|
686
731
|
var KVCacheStore = class {
|
|
687
732
|
ns;
|
|
@@ -689,16 +734,42 @@ var KVCacheStore = class {
|
|
|
689
734
|
this.ns = ns;
|
|
690
735
|
}
|
|
691
736
|
async get(key) {
|
|
692
|
-
|
|
693
|
-
|
|
737
|
+
return (await this.getEntry(key))?.value;
|
|
738
|
+
}
|
|
739
|
+
async getEntry(key) {
|
|
740
|
+
const { value, metadata } = await this.ns.getWithMetadata(key, "json");
|
|
741
|
+
if (value === null) return void 0;
|
|
742
|
+
if (typeof metadata === "object" && metadata !== null && "velaCacheExpiresAt" in metadata) {
|
|
743
|
+
const expiresAt = metadata.velaCacheExpiresAt;
|
|
744
|
+
if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= Date.now()) return void 0;
|
|
745
|
+
return {
|
|
746
|
+
value,
|
|
747
|
+
expiresAt
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
return { value };
|
|
694
751
|
}
|
|
695
752
|
async set(key, value, ttl) {
|
|
696
|
-
|
|
697
|
-
|
|
753
|
+
if (ttl !== void 0 && (!Number.isFinite(ttl) || ttl < 0)) throw new TypeError("Cache TTL must be finite and nonnegative.");
|
|
754
|
+
if (ttl !== void 0) return this.setEntry(key, {
|
|
755
|
+
value,
|
|
756
|
+
expiresAt: Date.now() + ttl * 1e3
|
|
757
|
+
});
|
|
758
|
+
await this.ns.put(key, JSON.stringify(value));
|
|
759
|
+
}
|
|
760
|
+
async setEntry(key, entry) {
|
|
761
|
+
if (!Number.isFinite(entry.expiresAt)) throw new TypeError("Cache expiry must be finite.");
|
|
762
|
+
const remaining = (entry.expiresAt - Date.now()) / 1e3;
|
|
763
|
+
if (remaining <= 0) return this.del(key);
|
|
764
|
+
await this.ns.put(key, JSON.stringify(entry.value), {
|
|
765
|
+
expirationTtl: Math.max(60, Math.ceil(remaining)),
|
|
766
|
+
metadata: { velaCacheExpiresAt: entry.expiresAt }
|
|
767
|
+
});
|
|
698
768
|
}
|
|
699
769
|
async del(key) {
|
|
700
770
|
await this.ns.delete(key);
|
|
701
771
|
}
|
|
772
|
+
/** Namespace-wide, best effort. Use a dedicated value namespace; never use for scoped invalidation. */
|
|
702
773
|
async clear() {
|
|
703
774
|
let cursor;
|
|
704
775
|
do {
|
|
@@ -708,6 +779,27 @@ var KVCacheStore = class {
|
|
|
708
779
|
} while (cursor);
|
|
709
780
|
}
|
|
710
781
|
};
|
|
782
|
+
/**
|
|
783
|
+
* Optional, eventually consistent generation store. Use a dedicated KV namespace
|
|
784
|
+
* without TTL/lifecycle cleanup. Never delete/reset generations while entries can
|
|
785
|
+
* survive. Concurrent writes and cached/negative reads prevent strong invalidation;
|
|
786
|
+
* this is unsuitable for strict read-after-write or authorization revocation.
|
|
787
|
+
*/
|
|
788
|
+
var KVCacheInvalidationStore = class {
|
|
789
|
+
ns;
|
|
790
|
+
constructor(ns) {
|
|
791
|
+
this.ns = ns;
|
|
792
|
+
}
|
|
793
|
+
async getVersion(key) {
|
|
794
|
+
const value = await this.ns.get(key, "json");
|
|
795
|
+
if (value === null) return "initial";
|
|
796
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 2048) throw new TypeError("Invalid cache generation.");
|
|
797
|
+
return value;
|
|
798
|
+
}
|
|
799
|
+
async invalidate(key) {
|
|
800
|
+
await this.ns.put(key, JSON.stringify(crypto.randomUUID()));
|
|
801
|
+
}
|
|
802
|
+
};
|
|
711
803
|
//#endregion
|
|
712
804
|
//#region src/services/flagship-flag.driver.ts
|
|
713
805
|
/**
|
|
@@ -846,6 +938,13 @@ registerEntrypointKind({
|
|
|
846
938
|
metaKey: SCHEDULED_METADATA_KEY,
|
|
847
939
|
level: "method"
|
|
848
940
|
});
|
|
941
|
+
function parseScheduledMetadata(value) {
|
|
942
|
+
if (typeof value !== "object" || value === null || !("cron" in value) || typeof value.cron !== "string" || !("methodName" in value) || typeof value.methodName !== "string" || !parseCron(value.cron, { dialect: "cloudflare" })) throw new TypeError("Invalid Cloudflare scheduled metadata");
|
|
943
|
+
return {
|
|
944
|
+
cron: value.cron,
|
|
945
|
+
methodName: value.methodName
|
|
946
|
+
};
|
|
947
|
+
}
|
|
849
948
|
/**
|
|
850
949
|
* Marks a method as a scheduled (cron) handler.
|
|
851
950
|
*
|
|
@@ -861,8 +960,9 @@ registerEntrypointKind({
|
|
|
861
960
|
* ```
|
|
862
961
|
*/
|
|
863
962
|
function Scheduled(cron) {
|
|
963
|
+
if (!parseCron(cron, { dialect: "cloudflare" })) throw new TypeError(`Invalid Cloudflare cron expression: ${cron}`);
|
|
864
964
|
return (target, propertyKey, _descriptor) => {
|
|
865
|
-
const existing =
|
|
965
|
+
const existing = getScheduledMetadata(target);
|
|
866
966
|
existing.push({
|
|
867
967
|
cron,
|
|
868
968
|
methodName: String(propertyKey)
|
|
@@ -870,6 +970,13 @@ function Scheduled(cron) {
|
|
|
870
970
|
defineMetadata(SCHEDULED_METADATA_KEY, existing, target.constructor);
|
|
871
971
|
};
|
|
872
972
|
}
|
|
973
|
+
function getScheduledMetadata(target) {
|
|
974
|
+
const ctor = typeof target === "function" ? target : target.constructor;
|
|
975
|
+
const value = getMetadata(SCHEDULED_METADATA_KEY, ctor);
|
|
976
|
+
if (value === void 0) return [];
|
|
977
|
+
if (!Array.isArray(value)) throw new TypeError("Invalid Cloudflare scheduled metadata list");
|
|
978
|
+
return value.map(parseScheduledMetadata);
|
|
979
|
+
}
|
|
873
980
|
//#endregion
|
|
874
981
|
//#region src/decorators/queue-consumer.ts
|
|
875
982
|
const QUEUE_CONSUMER_METADATA_KEY = "cloudflare:queue-consumer";
|
|
@@ -1018,6 +1125,6 @@ function durableObjectNonceStore(options) {
|
|
|
1018
1125
|
} };
|
|
1019
1126
|
}
|
|
1020
1127
|
//#endregion
|
|
1021
|
-
export { CloudflareApplication, CloudflareWebSocketModule, ConnectedSocket, DoCursorLog, DoPitrUnavailableError, Env, FlagshipFlagDriver, KVCacheStore, KvFlagDriver, MessageBody, QueueConsumer, R2StorageDriver, STORAGE_OPTIONS, Scheduled, StorageController, StorageManagerService, StorageModule, StorageService, SubscribeMessage, WebSocketGateway, WebSocketServer, WsException, armDoPitr, broadcastToRoom, cloudflareAdapter, cloudflareRateLimitStore, createCloudflareApp, createCloudflareWorker, durableObjectCursorLog, durableObjectLive, durableObjectNonceStore, durableObjectRoomName, flagshipFlagDriver, isDoPitrUnavailable, kvFlagDriver, liveInvalidateToRoom, readDoPitrBookmark };
|
|
1128
|
+
export { CloudflareApplication, CloudflareWebSocketModule, ConnectedSocket, DoCursorLog, DoPitrUnavailableError, Env, FlagshipFlagDriver, KVCacheInvalidationStore, KVCacheStore, KvFlagDriver, MessageBody, QueueConsumer, R2StorageDriver, STORAGE_OPTIONS, Scheduled, StorageController, StorageManagerService, StorageModule, StorageService, SubscribeMessage, WebSocketGateway, WebSocketServer, WsException, armDoPitr, broadcastToRoom, cloudflareAdapter, cloudflareRateLimitStore, createCloudflareApp, createCloudflareWorker, durableObjectCursorLog, durableObjectLive, durableObjectNonceStore, durableObjectRoomName, flagshipFlagDriver, isDoPitrUnavailable, kvFlagDriver, liveInvalidateToRoom, parseScheduledMetadata, readDoPitrBookmark };
|
|
1022
1129
|
|
|
1023
1130
|
//# sourceMappingURL=index.js.map
|