@fluojs/platform-cloudflare-workers
Advanced tools
+15
-6
@@ -1,4 +0,3 @@ | ||
| import { type Dispatcher, type HttpApplicationAdapter } from '@fluojs/http/internal'; | ||
| import { type BootstrapHttpAdapterApplicationOptions } from '@fluojs/runtime/internal/http-adapter'; | ||
| import type { Application, ModuleType, UploadedFile } from '@fluojs/runtime'; | ||
| import type { CorsOptions, Dispatcher, HttpApplicationAdapter, MiddlewareLike, SecurityHeadersOptions } from '@fluojs/http'; | ||
| import type { Application, CreateApplicationOptions, ModuleType } from '@fluojs/runtime'; | ||
| import { type CreateWebRequestResponseFactoryOptions } from '@fluojs/runtime/web'; | ||
@@ -8,6 +7,13 @@ declare module '@fluojs/http' { | ||
| cloudflare?: CloudflareWorkerRequestContext; | ||
| files?: UploadedFile[]; | ||
| rawBody?: Uint8Array; | ||
| } | ||
| } | ||
| declare const ADAPTER_CLOSE_SETTLED: unique symbol; | ||
| type CloudflareWorkerCorsInput = false | string | string[] | CorsOptions; | ||
| interface CloudflareWorkerMiddlewareOptions { | ||
| cors?: CloudflareWorkerCorsInput; | ||
| globalPrefix?: string; | ||
| globalPrefixExclude?: readonly string[]; | ||
| middleware?: MiddlewareLike[]; | ||
| securityHeaders?: false | SecurityHeadersOptions; | ||
| } | ||
| /** Minimal Worker execution context surface used by the adapter. */ | ||
@@ -59,3 +65,3 @@ export interface CloudflareWorkerExecutionContext { | ||
| /** Bootstrap options for constructing a Cloudflare Worker application shell. */ | ||
| export interface BootstrapCloudflareWorkerApplicationOptions extends BootstrapHttpAdapterApplicationOptions, CloudflareWorkerAdapterOptions { | ||
| export interface BootstrapCloudflareWorkerApplicationOptions extends Omit<CreateApplicationOptions, 'adapter' | 'middleware'>, CloudflareWorkerMiddlewareOptions, CloudflareWorkerAdapterOptions { | ||
| } | ||
@@ -86,2 +92,3 @@ /** Fetch handler shape exposed by Worker-backed application entrypoints. */ | ||
| private isClosed; | ||
| private isWebSocketBindingFrozen; | ||
| private websocketBinding?; | ||
@@ -94,2 +101,3 @@ private readonly options; | ||
| configureWebSocketBinding(binding: CloudflareWorkerWebSocketBinding | undefined): void; | ||
| [ADAPTER_CLOSE_SETTLED](): Promise<void>; | ||
| fetch<Env = unknown>(request: Request, env?: Env, executionContext?: CloudflareWorkerExecutionContext): Promise<Response>; | ||
@@ -133,2 +141,3 @@ listen(dispatcher: Dispatcher): Promise<void>; | ||
| } | ||
| export {}; | ||
| //# sourceMappingURL=adapter.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,sCAAsC,EAC5C,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,sCAAsC,EAE5C,MAAM,qBAAqB,CAAC;AAG7B,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,UAAU,CAAC,EAAE,8BAA8B,CAAC;QAC5C,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAMD,oEAAoE;AACpE,MAAM,WAAW,gCAAgC;IAC/C,sBAAsB,CAAC,IAAI,IAAI,CAAC;IAChC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5C;AAED,gGAAgG;AAChG,MAAM,WAAW,8BAA8B,CAAC,GAAG,GAAG,OAAO;IAC3D,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gCAAgC,CAAC;CAC9D;AAED,iEAAiE;AACjE,MAAM,MAAM,gCAAgC,GAAG,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,MAAM,CAAC;AAE7F,kFAAkF;AAClF,MAAM,WAAW,yBACf,SAAQ,IAAI,CAAC,SAAS,EAAE,kBAAkB,GAAG,OAAO,GAAG,qBAAqB,GAAG,MAAM,CAAC;IACtF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,iEAAiE;AACjE,MAAM,WAAW,6BAA6B;IAC5C,CAAC,EAAE,yBAAyB,CAAC;IAC7B,CAAC,EAAE,yBAAyB,CAAC;CAC9B;AAED,8EAA8E;AAC9E,MAAM,MAAM,oCAAoC,GAAG,MAAM,6BAA6B,CAAC;AAEvF,iFAAiF;AACjF,MAAM,WAAW,sCAAsC;IACrD,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,yBAAyB,CAAC;CACzC;AAED,gFAAgF;AAChF,MAAM,WAAW,oCAAoC;IACnD,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,sCAAsC,CAAC;CACnE;AAED,+FAA+F;AAC/F,MAAM,WAAW,gCAAgC;IAC/C,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,oCAAoC,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnG;AAED,yEAAyE;AACzE,MAAM,WAAW,oCAAoC;IACnD,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,SAAS,GAAG,IAAI,CAAC;CACxF;AAED,uEAAuE;AACvE,MAAM,WAAW,8BAA+B,SAAQ,sCAAsC;IAC5F,mBAAmB,CAAC,EAAE,oCAAoC,CAAC;CAC5D;AAED,gFAAgF;AAChF,MAAM,WAAW,2CACf,SAAQ,sCAAsC,EAC5C,8BAA8B;CAAG;AAErC,4EAA4E;AAC5E,MAAM,WAAW,uBAAuB,CAAC,GAAG,GAAG,OAAO;IACpD,KAAK,CACH,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,EACR,gBAAgB,EAAE,gCAAgC,GACjD,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtB;AAED,gEAAgE;AAChE,MAAM,WAAW,2BAA2B,CAAC,GAAG,GAAG,OAAO,CACxD,SAAQ,uBAAuB,CAAC,GAAG,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,sCAAsC,CAAC;IACzD,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAE1B,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,sEAAsE;AACtE,MAAM,WAAW,0BAA0B,CAAC,GAAG,GAAG,OAAO,CACvD,SAAQ,uBAAuB,CAAC,GAAG,CAAC;IACpC,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC,CAAC;CACpD;AAED;;GAEG;AACH,qBAAa,sCACX,YAAW,sBAAsB,EAAE,oCAAoC;IACvE,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAC,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAC;gBAE/B,OAAO,GAAE,8BAAmC;IAMlD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB5B,qBAAqB;IAOrB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,SAAS,GAAG,IAAI;IAIhF,KAAK,CAAC,GAAG,GAAG,OAAO,EACvB,OAAO,EAAE,OAAO,EAChB,GAAG,CAAC,EAAE,GAAG,EACT,gBAAgB,CAAC,EAAE,gCAAgC,GAClD,OAAO,CAAC,QAAQ,CAAC;IA+Cd,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IASnD,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,oBAAoB;YAqBd,uBAAuB;IAQrC,OAAO,CAAC,4BAA4B;CAerC;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,8BAAmC,GAC3C,sCAAsC,CAExC;AAED;;;;;;GAMG;AACH,wBAAsB,oCAAoC,CAAC,GAAG,GAAG,OAAO,EACtE,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,2CAAgD,GACxD,OAAO,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC,CAe3C;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,GAAG,GAAG,OAAO,EAC5D,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,2CAAgD,GACxD,0BAA0B,CAAC,GAAG,CAAC,CA6DjC;AAsKD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,YAAY;QACpB,SAAS,CAAC,EAAE,yBAAyB,CAAC;KACvC;IAED,UAAU,UAAU;QAClB,aAAa,CAAC,EAAE,UAAU,6BAA6B,CAAC;KACzD;CACF"} | ||
| {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"} |
+112
-34
@@ -6,2 +6,5 @@ import { createFetchStyleHttpAdapterRealtimeCapability } from '@fluojs/http/internal'; | ||
| const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000; | ||
| const WEBSOCKET_CLOSED_READY_STATE = 3; | ||
| const ADAPTER_CLOSE_SETTLED = Symbol('CloudflareWorkerAdapterCloseSettled'); | ||
| const WEBSOCKET_BINDING_RECONFIGURATION_MESSAGE = 'Cloudflare Workers websocket binding must be configured before listen() starts accepting Worker requests.'; | ||
@@ -47,2 +50,3 @@ /** Minimal Worker execution context surface used by the adapter. */ | ||
| isClosed = false; | ||
| isWebSocketBindingFrozen = false; | ||
| websocketBinding; | ||
@@ -80,4 +84,10 @@ options; | ||
| configureWebSocketBinding(binding) { | ||
| if (this.isWebSocketBindingFrozen && binding !== this.websocketBinding) { | ||
| throw new Error(WEBSOCKET_BINDING_RECONFIGURATION_MESSAGE); | ||
| } | ||
| this.websocketBinding = binding; | ||
| } | ||
| [ADAPTER_CLOSE_SETTLED]() { | ||
| return this.closeInFlight ?? Promise.resolve(); | ||
| } | ||
| async fetch(request, env, executionContext) { | ||
@@ -90,10 +100,15 @@ if (this.closeInFlight || this.isClosed) { | ||
| if (dispatcher && this.websocketBinding && isWebSocketUpgradeRequest(request)) { | ||
| const socketLifecycles = []; | ||
| try { | ||
| const response = await this.websocketBinding.fetch(request, { | ||
| upgrade: upgradeRequest => this.upgradeWebSocket(upgradeRequest) | ||
| upgrade: upgradeRequest => { | ||
| const upgrade = this.upgradeWebSocket(upgradeRequest); | ||
| socketLifecycles.push(createWebSocketCloseLifecycle(upgrade.serverSocket)); | ||
| return upgrade; | ||
| } | ||
| }); | ||
| executionContext?.waitUntil(Promise.resolve()); | ||
| return response; | ||
| } finally { | ||
| release(); | ||
| const lifecycle = Promise.all(socketLifecycles).then(() => undefined).finally(release); | ||
| executionContext?.waitUntil(lifecycle); | ||
| } | ||
@@ -123,2 +138,3 @@ } | ||
| this.isClosed = false; | ||
| this.isWebSocketBindingFrozen = true; | ||
| this.dispatcher = dispatcher; | ||
@@ -216,4 +232,8 @@ } | ||
| let closeInFlight; | ||
| let closeRecovery; | ||
| let runningApplication; | ||
| const ready = async () => { | ||
| if (closeRecovery) { | ||
| await closeRecovery; | ||
| } | ||
| if (closeError) { | ||
@@ -233,2 +253,5 @@ throw closeError; | ||
| } | ||
| if (closeRecovery) { | ||
| await closeRecovery; | ||
| } | ||
| if (closeError) { | ||
@@ -242,4 +265,6 @@ throw closeError; | ||
| const closing = (async () => { | ||
| let currentApplication; | ||
| try { | ||
| await (await application).close(signal); | ||
| currentApplication = await application; | ||
| await currentApplication.close(signal); | ||
| if (runningApplication === application) { | ||
@@ -249,3 +274,19 @@ runningApplication = undefined; | ||
| } catch (error) { | ||
| closeError = error; | ||
| if (currentApplication && isShutdownTimeoutError(error)) { | ||
| closeRecovery = watchTimedOutCloseRecovery(currentApplication, { | ||
| clearRunningApplication() { | ||
| if (runningApplication === application) { | ||
| runningApplication = undefined; | ||
| } | ||
| }, | ||
| setCloseError(error) { | ||
| closeError = error; | ||
| }, | ||
| setCloseRecovery(recovery) { | ||
| closeRecovery = recovery; | ||
| } | ||
| }); | ||
| } else { | ||
| closeError = error; | ||
| } | ||
| throw error; | ||
@@ -260,3 +301,3 @@ } finally { | ||
| async fetch(request, env, executionContext) { | ||
| if (closeError || closeInFlight) { | ||
| if (closeError || closeInFlight || closeRecovery) { | ||
| return createShutdownResponse(); | ||
@@ -327,2 +368,15 @@ } | ||
| } | ||
| function createWebSocketCloseLifecycle(socket) { | ||
| if (socket.readyState === WEBSOCKET_CLOSED_READY_STATE) { | ||
| return Promise.resolve(); | ||
| } | ||
| const lifecycle = createDeferred(); | ||
| socket.addEventListener('close', () => lifecycle.resolve(), { | ||
| once: true | ||
| }); | ||
| if (socket.readyState === WEBSOCKET_CLOSED_READY_STATE) { | ||
| lifecycle.resolve(); | ||
| } | ||
| return lifecycle.promise; | ||
| } | ||
| function createDeferred() { | ||
@@ -341,2 +395,21 @@ let resolve; | ||
| } | ||
| function watchTimedOutCloseRecovery(currentApplication, callbacks) { | ||
| const recovery = currentApplication.adapter[ADAPTER_CLOSE_SETTLED]().then(() => { | ||
| callbacks.clearRunningApplication(); | ||
| }, error => { | ||
| callbacks.setCloseError(error); | ||
| throw error; | ||
| }); | ||
| const recoveryWithCleanup = recovery.finally(() => { | ||
| callbacks.setCloseRecovery(undefined); | ||
| }); | ||
| void recoveryWithCleanup.catch(() => undefined); | ||
| return recoveryWithCleanup; | ||
| } | ||
| function createShutdownTimeoutMessage(timeoutMs) { | ||
| return `Cloudflare Workers adapter shutdown timeout exceeded ${String(timeoutMs)}ms.`; | ||
| } | ||
| function isShutdownTimeoutError(error) { | ||
| return error instanceof Error && error.message === createShutdownTimeoutMessage(DEFAULT_SHUTDOWN_TIMEOUT_MS); | ||
| } | ||
| function createShutdownResponse() { | ||
@@ -373,32 +446,37 @@ return new Response(JSON.stringify({ | ||
| } | ||
| const reader = responseBody.getReader(); | ||
| const trackedBody = new ReadableStream({ | ||
| async cancel(reason) { | ||
| try { | ||
| await reader.cancel(reason); | ||
| lifecycle.resolve(); | ||
| } catch (error) { | ||
| lifecycle.reject(error); | ||
| throw error; | ||
| } | ||
| }, | ||
| async pull(controller) { | ||
| try { | ||
| const result = await reader.read(); | ||
| if (result.done) { | ||
| controller.close(); | ||
| try { | ||
| const reader = responseBody.getReader(); | ||
| const trackedBody = new ReadableStream({ | ||
| async cancel(reason) { | ||
| try { | ||
| await reader.cancel(reason); | ||
| lifecycle.resolve(); | ||
| return; | ||
| } catch (error) { | ||
| lifecycle.reject(error); | ||
| throw error; | ||
| } | ||
| controller.enqueue(result.value); | ||
| } catch (error) { | ||
| controller.error(error); | ||
| lifecycle.reject(error); | ||
| }, | ||
| async pull(controller) { | ||
| try { | ||
| const result = await reader.read(); | ||
| if (result.done) { | ||
| controller.close(); | ||
| lifecycle.resolve(); | ||
| return; | ||
| } | ||
| controller.enqueue(result.value); | ||
| } catch (error) { | ||
| controller.error(error); | ||
| lifecycle.reject(error); | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| lifecycle: lifecycle.promise.finally(release), | ||
| response: new Response(trackedBody, response) | ||
| }; | ||
| }); | ||
| return { | ||
| lifecycle: lifecycle.promise.finally(release), | ||
| response: new Response(trackedBody, response) | ||
| }; | ||
| } catch (error) { | ||
| release(); | ||
| throw error; | ||
| } | ||
| } | ||
@@ -411,3 +489,3 @@ function isLifecycleTrackedStreamingResponse(response) { | ||
| const timeoutHandle = setTimeout(() => { | ||
| reject(new Error(`Cloudflare Workers adapter shutdown timeout exceeded ${String(timeoutMs)}ms.`)); | ||
| reject(new Error(createShutdownTimeoutMessage(timeoutMs))); | ||
| }, timeoutMs); | ||
@@ -414,0 +492,0 @@ void closePromise.then(() => { |
+3
-3
@@ -12,3 +12,3 @@ { | ||
| ], | ||
| "version": "1.0.4", | ||
| "version": "1.0.5", | ||
| "private": false, | ||
@@ -37,4 +37,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@fluojs/http": "^1.1.2", | ||
| "@fluojs/runtime": "^1.1.8" | ||
| "@fluojs/http": "^2.0.1", | ||
| "@fluojs/runtime": "^2.0.1" | ||
| }, | ||
@@ -41,0 +41,0 @@ "devDependencies": { |
+30
-9
@@ -13,2 +13,3 @@ # @fluojs/platform-cloudflare-workers | ||
| - [주요 패턴](#주요-패턴) | ||
| - [Lifecycle 및 public seam 참고](#lifecycle-및-public-seam-참고) | ||
| - [Conformance 커버리지](#conformance-커버리지) | ||
@@ -31,5 +32,5 @@ - [공개 API 개요](#공개-api-개요) | ||
| 이 어댑터는 dispatcher가 binding된 뒤 각 요청 수명주기를 `executionContext.waitUntil(...)`에 연결하고, `close()` 중에도 진행 중인 디스패치와 SSE(`text/event-stream`) response body를 유지하여 Worker 종료 도중 활성 작업이 중간에 잘리지 않도록 보장합니다. | ||
| 이 어댑터는 dispatcher가 binding된 뒤 각 요청 수명주기를 `executionContext.waitUntil(...)`에 연결하고, `close()` 중에도 진행 중인 디스패치, terminal close까지의 upgraded server WebSocket, SSE(`text/event-stream`) response body를 유지하여 Worker 종료 도중 활성 작업이 중간에 잘리지 않도록 보장합니다. | ||
| 애플리케이션 종료 중에는 즉시 새 ingress 수락을 중단하고, 활성 HTTP 핸들러가 정리될 수 있도록 최대 10초의 bounded drain window를 제공합니다. 이 시간을 넘기면 `close()`는 무기한 대기하지 않고 timeout 오류로 종료됩니다. 해당 drain이 아직 진행 중일 때 동시에 `listen()`을 호출하면 Worker를 다시 열지 않고 `Cloudflare Workers adapter cannot listen while shutdown is still draining.` 오류로 reject됩니다. 닫힌 뒤에는 어댑터가 명시적으로 다시 `listen()`될 때까지 후속 HTTP 및 WebSocket upgrade request가 동일한 JSON `503` shutdown response를 받습니다. | ||
| 애플리케이션 종료 중에는 즉시 새 ingress 수락을 중단하고, 활성 HTTP 핸들러가 정리될 수 있도록 최대 10초의 bounded drain window를 제공합니다. 이 시간을 넘기면 `close()`는 무기한 대기하지 않고 timeout 오류로 종료됩니다. 해당 drain이 아직 진행 중일 때 동시에 `listen()`을 호출하면 Worker를 다시 열지 않고 `Cloudflare Workers adapter cannot listen while shutdown is still draining.` 오류로 reject됩니다. 닫힌 뒤에는 어댑터가 명시적으로 다시 `listen()`될 때까지 후속 HTTP 및 WebSocket upgrade request가 동일한 JSON `503` shutdown response를 받습니다. Lazy entrypoint는 timed-out close가 아직 drain 중인 동안 shutdown response를 계속 반환하지만, underlying close가 나중에 settle되면 해당 임시 gate를 해제하여 이후 request가 새 Worker application을 bootstrap할 수 있게 합니다. | ||
@@ -73,9 +74,23 @@ ## 빠른 시작 | ||
| ### WebSocketPair 활용 | ||
| 어댑터는 `@fluojs/websockets/cloudflare-workers` 바인딩을 통해 실시간 통신을 위한 Cloudflare의 네이티브 `WebSocketPair`를 지원합니다. Upgrade handling은 해당 binding을 통한 opt-in이며, non-hosted runtime test에서는 `createWebSocketPair`를 주입할 수 있습니다. | ||
| 어댑터는 `@fluojs/websockets/cloudflare-workers` 바인딩을 통해 실시간 통신을 위한 Cloudflare의 네이티브 `WebSocketPair`를 지원합니다. Upgrade handling은 해당 binding을 통한 opt-in이며, non-hosted runtime test에서는 `createWebSocketPair`를 주입할 수 있습니다. Binding은 `listen()`이 Worker dispatch boundary를 시작하기 전에 설정하세요. `listen()`이 한 번 실행된 뒤에는 해당 adapter instance의 binding identity가 frozen됩니다. 이미 public listen boundary를 지난 isolate 아래에서 upgrade ownership이 바뀌지 않도록, `close()` 이후에도 binding을 교체하거나 해제하려는 시도는 reject됩니다. | ||
| ```typescript | ||
| import { Module } from '@fluojs/core'; | ||
| import { | ||
| CloudflareWorkersWebSocketModule, | ||
| WebSocketGateway, | ||
| } from '@fluojs/websockets/cloudflare-workers'; | ||
| @WebSocketGateway({ path: '/ws' }) | ||
| export class MyGateway {} | ||
| export class EdgeGateway {} | ||
| @Module({ | ||
| imports: [CloudflareWorkersWebSocketModule.forRoot()], | ||
| providers: [EdgeGateway], | ||
| }) | ||
| export class RealtimeModule {} | ||
| ``` | ||
| Bootstrap 전에 application module graph에 `RealtimeModule`을 import하세요. Application bootstrap 중 `CloudflareWorkersWebSocketModule`이 gateway를 발견하고 `app.listen()`이 binding을 freeze하기 전에 Worker adapter binding을 구성합니다. Listen boundary 이후에는 binding을 추가하거나 교체하지 마세요. | ||
| ### 엣지 네이티브 미들웨어 | ||
@@ -93,12 +108,18 @@ 표준 fluo 미들웨어(CORS, Global Prefix 등)는 Worker bootstrap helper를 통해 완전히 지원되며 Cloudflare 환경에 최적화되어 있습니다. `createCloudflareWorkerAdapter(...)`는 adapter가 소유하는 parsing 및 websocket-pair 옵션만 받습니다. Routing 및 middleware 옵션은 `bootstrapCloudflareWorkerApplication(...)` 또는 `createCloudflareWorkerEntrypoint(...)`에 전달하세요. | ||
| - `fetch()`는 `listen()` 또는 lazy entrypoint가 dispatcher를 binding한 뒤 active work를 `executionContext.waitUntil(...)`에 등록합니다. SSE(`text/event-stream`) response는 body가 끝나거나 cancel될 때까지 해당 lifecycle과 close drain을 유지합니다. 그 lifecycle boundary 전에는 upgrade request와 HTTP dispatch가 application handler에 도달하지 않습니다. | ||
| - `fetch()`는 `listen()` 또는 lazy entrypoint가 dispatcher를 binding한 뒤 active work를 `executionContext.waitUntil(...)`에 등록합니다. Upgraded server WebSocket은 terminal `close` event까지 해당 lifecycle과 close drain을 유지하고, SSE(`text/event-stream`) response는 body가 끝나거나 cancel될 때까지 이를 유지합니다. SSE reader 또는 tracked-stream setup이 동기적으로 실패하면 오류를 전파하기 전에 lifecycle을 release합니다. 그 lifecycle boundary 전에는 upgrade request와 HTTP dispatch가 application handler에 도달하지 않습니다. | ||
| - `maxBodySize` 같은 adapter option은 Worker adapter 생성 시 검증됩니다. `globalPrefix`, `cors`, `middleware`, `securityHeaders` 같은 bootstrap 전용 옵션은 `createCloudflareWorkerAdapter(...)`가 아니라 Worker bootstrap helper에 전달해야 합니다. | ||
| - WebSocket upgrade는 HTTP dispatch와 같은 listen boundary가 소유합니다. `listen()` 전의 upgrade request는 설정된 binding에 도달하지 않습니다. | ||
| - `close()`는 shutdown 중 및 shutdown 이후 새 요청에 JSON `503` response를 반환하고, active request가 끝나지 않으면 10초 뒤 timeout됩니다. 해당 close drain이 아직 활성 상태일 때 `listen()`을 호출하면 Cloudflare Workers adapter shutdown-draining 오류로 reject됩니다. | ||
| - WebSocket upgrade는 HTTP dispatch와 같은 listen boundary가 소유합니다. `listen()` 전의 upgrade request는 설정된 binding에 도달하지 않으며, adapter가 한 번이라도 listen한 뒤 defined binding을 교체하거나 해제하려는 시도는 Worker upgrade ownership을 바꾸는 대신 빠르게 실패합니다. 다른 websocket binding이 필요하면 새 adapter를 생성하세요. | ||
| - `close()`는 shutdown 중 및 shutdown 이후 새 HTTP 및 WebSocket upgrade request에 JSON `503` response를 반환하고, active request가 끝나지 않으면 10초 뒤 timeout됩니다. 해당 close drain이 아직 활성 상태일 때 `listen()`을 호출하면 Cloudflare Workers adapter shutdown-draining 오류로 reject됩니다. Lazy entrypoint는 adapter의 underlying drain이 나중에 끝나면 이 timeout을 영구적으로 캐시하지 않습니다. | ||
| - Multipart request는 `rawBody`를 보존하지 않습니다. | ||
| - Worker `env` 객체는 각 `FrameworkRequest`에 `request.cloudflare.env`로 연결되고 Worker execution context는 `request.cloudflare.executionContext`로 제공됩니다. Package-level config resolution은 application이 소유하므로, binding은 application boundary에서 명시적 provider 또는 `@fluojs/config`로 매핑하세요. | ||
| ## Lifecycle 및 public seam 참고 | ||
| Root `@fluojs/platform-cloudflare-workers` export는 application code와 first-party Worker websocket integration이 사용하는 Worker public seam을 소유합니다. `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, `CloudflareWorkerWebSocketPair`, `CloudflareWorkerWebSocketPairFactory`, `CloudflareWorkerWebSocketUpgradeHost`, `CloudflareWorkerWebSocketUpgradeResult` 같은 Worker-specific public type은 consumer가 `@fluojs/http/internal` 또는 `@fluojs/runtime/internal*` subpath를 import하지 않아도 되도록 이 패키지에서 export됩니다. | ||
| 위의 listen, shutdown, SSE drain, websocket binding 규칙은 public lifecycle behavior입니다. 이러한 public seam type 또는 lifecycle semantic을 바꾸는 변경은 `@fluojs/platform-cloudflare-workers` release governance 대상이며, user-impacting update는 implementation, docs, tests와 함께 Changesets로 추적해야 합니다. | ||
| ## Conformance 커버리지 | ||
| `packages/platform-cloudflare-workers/src/adapter.test.ts`는 문서화된 Worker 계약을 검증하는 package-local regression 대상입니다. 이 파일은 shared Web dispatch delegation, Worker `env` request attachment, `executionContext.waitUntil(...)` SSE(`text/event-stream`) body tracking, websocket upgrade binding, listen-bound upgrade ownership, lazy entrypoint 재사용, shutdown gating, drain 중 `listen()` rejection, close 중 및 close 이후 JSON `503` response, bounded 10초 close timeout을 검증합니다. | ||
| `packages/platform-cloudflare-workers/src/adapter.test.ts`와 `packages/platform-cloudflare-workers/src/adapter-lifecycle.test.ts`는 문서화된 Worker 계약을 검증하는 package-local regression 대상입니다. 이 파일들은 shared Web dispatch delegation, Worker `env` request attachment, `executionContext.waitUntil(...)` SSE(`text/event-stream`) body tracking, body-cancellation 및 synchronous setup-failure drain, websocket upgrade binding, upgraded server-socket close tracking, pre-listen HTTP 및 websocket lifecycle guard, listen boundary 이후 websocket binding freeze, lazy entrypoint 재사용 및 timeout recovery, shutdown gating, drain 중 `listen()` rejection, HTTP와 websocket upgrade 모두에 대한 close 중 및 close 이후 JSON `503` response, reliable fake-timer cleanup, public seam source import, README parity, bounded 10초 close timeout을 검증합니다. | ||
@@ -116,3 +137,3 @@ 공유 edge portability suite인 `packages/testing/src/portability/web-runtime-adapter-portability.test.ts`는 Cloudflare Workers를 Bun 및 Deno와 함께 실행해 malformed cookie 보존, query decoding, JSON/text raw-body capture, multipart raw-body 제외, SSE framing을 검증합니다. 패키지 테스트의 README parity assertion은 이 edge-runtime 커버리지 문서가 한국어 mirror와 계속 동기화되도록 확인합니다. | ||
| - `CloudflareWorkerEntrypoint`: `fetch`, `ready()`, `close()` lifecycle method를 제공하는 lazy entrypoint입니다. | ||
| - Option 및 type: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, Worker websocket pair/upgrade type. | ||
| - Option 및 type: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, `CloudflareWorkerWebSocketBindingHost`, `CloudflareWorkerWebSocket`, `CloudflareWorkerWebSocketMessage`, `CloudflareWorkerWebSocketPair`, `CloudflareWorkerWebSocketPairFactory`, `CloudflareWorkerWebSocketUpgradeHost`, `CloudflareWorkerWebSocketUpgradeResult`. | ||
@@ -119,0 +140,0 @@ ## 관련 패키지 |
+30
-9
@@ -13,2 +13,3 @@ # @fluojs/platform-cloudflare-workers | ||
| - [Common Patterns](#common-patterns) | ||
| - [Lifecycle and Public Seam Notes](#lifecycle-and-public-seam-notes) | ||
| - [Conformance Coverage](#conformance-coverage) | ||
@@ -31,5 +32,5 @@ - [Public API Overview](#public-api-overview) | ||
| The adapter binds each request lifecycle to `executionContext.waitUntil(...)` after the dispatcher is bound and keeps in-flight dispatches and SSE (`text/event-stream`) response bodies alive during `close()` so Worker shutdown does not drop active work mid-request. | ||
| The adapter binds each request lifecycle to `executionContext.waitUntil(...)` after the dispatcher is bound and keeps in-flight dispatches, WebSocket upgrades through the upgraded server socket's terminal close, and SSE (`text/event-stream`) response bodies alive during `close()` so Worker shutdown does not drop active work mid-request. | ||
| During application shutdown, the adapter stops accepting new ingress immediately and gives active HTTP handlers a bounded 10-second drain window before `close()` fails with a timeout instead of hanging indefinitely. While that drain is still in progress, a concurrent `listen()` call rejects with `Cloudflare Workers adapter cannot listen while shutdown is still draining.` instead of reopening the Worker. Once closed, follow-up HTTP and WebSocket upgrade requests receive the same JSON `503` shutdown response until the adapter is explicitly listened again. | ||
| During application shutdown, the adapter stops accepting new ingress immediately and gives active HTTP handlers a bounded 10-second drain window before `close()` fails with a timeout instead of hanging indefinitely. While that drain is still in progress, a concurrent `listen()` call rejects with `Cloudflare Workers adapter cannot listen while shutdown is still draining.` instead of reopening the Worker. Once closed, follow-up HTTP and WebSocket upgrade requests receive the same JSON `503` shutdown response until the adapter is explicitly listened again. Lazy entrypoints keep returning shutdown responses while a timed-out close is still draining, but they clear that temporary gate once the underlying close eventually settles so a later request can bootstrap a fresh Worker application. | ||
@@ -73,9 +74,23 @@ ## Quick Start | ||
| ### Working with WebSocketPairs | ||
| The adapter supports Cloudflare's native `WebSocketPair` for real-time communication via the `@fluojs/websockets/cloudflare-workers` binding. Upgrade handling is opt-in through that binding, and `createWebSocketPair` can be injected for non-hosted runtime tests. | ||
| The adapter supports Cloudflare's native `WebSocketPair` for real-time communication via the `@fluojs/websockets/cloudflare-workers` binding. Upgrade handling is opt-in through that binding, and `createWebSocketPair` can be injected for non-hosted runtime tests. Configure the binding before `listen()` starts the Worker dispatch boundary; once `listen()` has run, the binding identity is frozen for that adapter instance. Replacing or clearing it is rejected even after `close()`, so upgrade ownership cannot change underneath an isolate that has already crossed the public listen boundary. | ||
| ```typescript | ||
| import { Module } from '@fluojs/core'; | ||
| import { | ||
| CloudflareWorkersWebSocketModule, | ||
| WebSocketGateway, | ||
| } from '@fluojs/websockets/cloudflare-workers'; | ||
| @WebSocketGateway({ path: '/ws' }) | ||
| export class MyGateway {} | ||
| export class EdgeGateway {} | ||
| @Module({ | ||
| imports: [CloudflareWorkersWebSocketModule.forRoot()], | ||
| providers: [EdgeGateway], | ||
| }) | ||
| export class RealtimeModule {} | ||
| ``` | ||
| Import `RealtimeModule` into the application module graph before bootstrap. During application bootstrap, `CloudflareWorkersWebSocketModule` discovers the gateway and configures the Worker adapter binding before `app.listen()` freezes it; do not add or replace the binding after the listen boundary. | ||
| ### Edge-Native Middleware | ||
@@ -93,12 +108,18 @@ Standard fluo middleware (CORS, Global Prefix, etc.) is fully supported through Worker bootstrap helpers and optimized for the Cloudflare environment. `createCloudflareWorkerAdapter(...)` only accepts adapter-owned parsing and websocket-pair options; pass routing and middleware options to `bootstrapCloudflareWorkerApplication(...)` or `createCloudflareWorkerEntrypoint(...)` instead. | ||
| - `fetch()` registers active work with `executionContext.waitUntil(...)` after `listen()` or the lazy entrypoint binds the dispatcher; SSE (`text/event-stream`) responses keep that lifecycle and the close drain open until the body finishes or is canceled. Before that lifecycle boundary, upgrade requests and HTTP dispatch do not reach application handlers. | ||
| - `fetch()` registers active work with `executionContext.waitUntil(...)` after `listen()` or the lazy entrypoint binds the dispatcher; upgraded server WebSockets keep that lifecycle and the close drain open until their terminal `close` event, while SSE (`text/event-stream`) responses keep them open until the body finishes or is canceled. Synchronous SSE reader or tracked-stream setup failures release the lifecycle before propagating. Before that lifecycle boundary, upgrade requests and HTTP dispatch do not reach application handlers. | ||
| - Adapter options such as `maxBodySize` are validated when the Worker adapter is created; bootstrap-only options such as `globalPrefix`, `cors`, `middleware`, and `securityHeaders` belong on Worker bootstrap helpers rather than `createCloudflareWorkerAdapter(...)`. | ||
| - WebSocket upgrades are owned by the same listen boundary as HTTP dispatch; upgrade requests before `listen()` do not reach the configured binding. | ||
| - `close()` returns JSON `503` responses for new requests during and after shutdown and times out after 10 seconds if active requests never settle. Calling `listen()` while that close drain is still active rejects with the Cloudflare Workers adapter shutdown-draining error. | ||
| - WebSocket upgrades are owned by the same listen boundary as HTTP dispatch; upgrade requests before `listen()` do not reach the configured binding, and attempts to replace or clear a defined binding after the adapter has ever listened fail fast instead of mutating Worker upgrade ownership. Create a new adapter when a host needs a different websocket binding. | ||
| - `close()` returns JSON `503` responses for new HTTP and WebSocket upgrade requests during and after shutdown and times out after 10 seconds if active requests never settle. Calling `listen()` while that close drain is still active rejects with the Cloudflare Workers adapter shutdown-draining error. Lazy entrypoints do not permanently cache that timeout once the adapter's underlying drain later finishes. | ||
| - Multipart requests do not preserve `rawBody`. | ||
| - The Worker `env` object is attached to each `FrameworkRequest` as `request.cloudflare.env`, with the Worker execution context available as `request.cloudflare.executionContext`; package-level config resolution remains application-owned, so map bindings into explicit providers or `@fluojs/config` at the application boundary. | ||
| ## Lifecycle and Public Seam Notes | ||
| The root `@fluojs/platform-cloudflare-workers` export owns the Worker public seam for application code and first-party Worker websocket integrations. Worker-specific public types such as `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, `CloudflareWorkerWebSocketPair`, `CloudflareWorkerWebSocketPairFactory`, `CloudflareWorkerWebSocketUpgradeHost`, and `CloudflareWorkerWebSocketUpgradeResult` are exported from this package instead of asking consumers to import `@fluojs/http/internal` or `@fluojs/runtime/internal*` subpaths. | ||
| The listen, shutdown, SSE drain, and websocket binding rules above are public lifecycle behavior. Changes to those public seam types or lifecycle semantics are release-governed for `@fluojs/platform-cloudflare-workers`; user-impacting updates must be tracked with Changesets alongside the implementation, docs, and tests. | ||
| ## Conformance Coverage | ||
| `packages/platform-cloudflare-workers/src/adapter.test.ts` is the package-local regression target for the documented Worker contract. It covers shared Web dispatch delegation, Worker `env` request attachment, `executionContext.waitUntil(...)` SSE (`text/event-stream`) body tracking, websocket upgrade binding, listen-bound upgrade ownership, lazy entrypoint reuse, shutdown gating, drain-time `listen()` rejection, JSON `503` responses while closing and after close, and the bounded 10-second close timeout. | ||
| `packages/platform-cloudflare-workers/src/adapter.test.ts` and `packages/platform-cloudflare-workers/src/adapter-lifecycle.test.ts` are the package-local regression targets for the documented Worker contract. They cover shared Web dispatch delegation, Worker `env` request attachment, `executionContext.waitUntil(...)` SSE (`text/event-stream`) body tracking, body-cancellation and synchronous setup-failure drains, websocket upgrade binding, upgraded server-socket close tracking, pre-listen HTTP and websocket lifecycle guards, websocket binding freeze after the listen boundary, lazy entrypoint reuse and timeout recovery, shutdown gating, drain-time `listen()` rejection, JSON `503` responses while closing and after close for both HTTP and websocket upgrades, reliable fake-timer cleanup, public seam source imports, README parity, and the bounded 10-second close timeout. | ||
@@ -116,3 +137,3 @@ The shared edge portability suite in `packages/testing/src/portability/web-runtime-adapter-portability.test.ts` exercises Cloudflare Workers beside Bun and Deno for malformed cookie preservation, query decoding, JSON/text raw-body capture, multipart raw-body exclusion, and SSE framing. The README parity assertion in the package test keeps these documented edge-runtime coverage claims synchronized with the Korean mirror. | ||
| - `CloudflareWorkerEntrypoint`: Lazy entrypoint with `fetch`, `ready()`, and `close()` lifecycle methods. | ||
| - Options and types: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, and Worker websocket pair/upgrade types. | ||
| - Options and types: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, `CloudflareWorkerWebSocketBindingHost`, `CloudflareWorkerWebSocket`, `CloudflareWorkerWebSocketMessage`, `CloudflareWorkerWebSocketPair`, `CloudflareWorkerWebSocketPairFactory`, `CloudflareWorkerWebSocketUpgradeHost`, and `CloudflareWorkerWebSocketUpgradeResult`. | ||
@@ -119,0 +140,0 @@ ## Related Packages |
51769
25.81%604
16.83%145
16.94%+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated
Updated