@fluojs/platform-cloudflare-workers
Advanced tools
@@ -7,2 +7,3 @@ import { type Dispatcher, type HttpApplicationAdapter } from '@fluojs/http/internal'; | ||
| interface FrameworkRequest { | ||
| cloudflare?: CloudflareWorkerRequestContext; | ||
| files?: UploadedFile[]; | ||
@@ -17,2 +18,7 @@ rawBody?: Uint8Array; | ||
| } | ||
| /** Worker-specific request context attached to fluo HTTP requests by the Cloudflare adapter. */ | ||
| export interface CloudflareWorkerRequestContext<Env = unknown> { | ||
| readonly env: Env; | ||
| readonly executionContext?: CloudflareWorkerExecutionContext; | ||
| } | ||
| /** Message payloads accepted by Cloudflare Worker websockets. */ | ||
@@ -87,3 +93,3 @@ export type CloudflareWorkerWebSocketMessage = ArrayBuffer | ArrayBufferView | Blob | string; | ||
| configureWebSocketBinding(binding: CloudflareWorkerWebSocketBinding | undefined): void; | ||
| fetch<Env = unknown>(request: Request, _env?: Env, executionContext?: CloudflareWorkerExecutionContext): Promise<Response>; | ||
| fetch<Env = unknown>(request: Request, env?: Env, executionContext?: CloudflareWorkerExecutionContext): Promise<Response>; | ||
| listen(dispatcher: Dispatcher): Promise<void>; | ||
@@ -93,2 +99,3 @@ private upgradeWebSocket; | ||
| private waitForInFlightRequests; | ||
| private createRequestResponseFactory; | ||
| } | ||
@@ -95,0 +102,0 @@ /** |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"} |
+90
-15
@@ -9,2 +9,4 @@ import { createFetchStyleHttpAdapterRealtimeCapability } from '@fluojs/http/internal'; | ||
| /** Worker-specific request context attached to fluo HTTP requests by the Cloudflare adapter. */ | ||
| /** Message payloads accepted by Cloudflare Worker websockets. */ | ||
@@ -79,3 +81,3 @@ | ||
| } | ||
| async fetch(request, _env, executionContext) { | ||
| async fetch(request, env, executionContext) { | ||
| if (this.closeInFlight || this.isClosed) { | ||
@@ -85,22 +87,30 @@ return createShutdownResponse(); | ||
| const release = this.trackInFlightRequest(); | ||
| const responsePromise = (async () => { | ||
| const dispatcher = this.dispatcher; | ||
| if (dispatcher && this.websocketBinding && isWebSocketUpgradeRequest(request)) { | ||
| try { | ||
| const dispatcher = this.dispatcher; | ||
| if (dispatcher && this.websocketBinding && isWebSocketUpgradeRequest(request)) { | ||
| return await this.websocketBinding.fetch(request, { | ||
| upgrade: upgradeRequest => this.upgradeWebSocket(upgradeRequest) | ||
| }); | ||
| } | ||
| return await dispatchWebRequest({ | ||
| dispatcher, | ||
| dispatcherNotReadyMessage: WORKER_DISPATCHER_NOT_READY_MESSAGE, | ||
| factory: this.webRequestResponseFactory, | ||
| request | ||
| const response = await this.websocketBinding.fetch(request, { | ||
| upgrade: upgradeRequest => this.upgradeWebSocket(upgradeRequest) | ||
| }); | ||
| executionContext?.waitUntil(Promise.resolve()); | ||
| return response; | ||
| } finally { | ||
| release(); | ||
| } | ||
| } | ||
| const responsePromise = (async () => { | ||
| return await dispatchWebRequest({ | ||
| dispatcher, | ||
| dispatcherNotReadyMessage: WORKER_DISPATCHER_NOT_READY_MESSAGE, | ||
| factory: this.createRequestResponseFactory(env, executionContext), | ||
| request | ||
| }); | ||
| })(); | ||
| executionContext?.waitUntil(responsePromise.then(() => undefined, () => undefined)); | ||
| return await responsePromise; | ||
| const trackedResponsePromise = responsePromise.then(response => createLifecycleTrackedResponse(response, release), error => { | ||
| release(); | ||
| throw error; | ||
| }); | ||
| executionContext?.waitUntil(trackedResponsePromise.then(({ | ||
| lifecycle | ||
| }) => lifecycle).then(() => undefined, () => undefined)); | ||
| return (await trackedResponsePromise).response; | ||
| } | ||
@@ -145,2 +155,16 @@ async listen(dispatcher) { | ||
| } | ||
| createRequestResponseFactory(env, executionContext) { | ||
| const baseFactory = this.webRequestResponseFactory; | ||
| return { | ||
| ...baseFactory, | ||
| async createRequest(request, signal) { | ||
| const frameworkRequest = await baseFactory.createRequest(request, signal); | ||
| frameworkRequest.cloudflare = { | ||
| env, | ||
| executionContext | ||
| }; | ||
| return frameworkRequest; | ||
| } | ||
| }; | ||
| } | ||
| } | ||
@@ -324,2 +348,53 @@ | ||
| } | ||
| function createLifecycleTrackedResponse(response, release) { | ||
| if (!isLifecycleTrackedStreamingResponse(response)) { | ||
| release(); | ||
| return { | ||
| lifecycle: Promise.resolve(), | ||
| response | ||
| }; | ||
| } | ||
| const lifecycle = createDeferred(); | ||
| const responseBody = response.body; | ||
| if (!responseBody) { | ||
| release(); | ||
| return { | ||
| lifecycle: Promise.resolve(), | ||
| response | ||
| }; | ||
| } | ||
| 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(); | ||
| 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) | ||
| }; | ||
| } | ||
| function isLifecycleTrackedStreamingResponse(response) { | ||
| return response.body !== null && response.headers.get('content-type')?.toLowerCase().includes('text/event-stream') === true; | ||
| } | ||
| function waitForCloseWithTimeout(closePromise, timeoutMs) { | ||
@@ -326,0 +401,0 @@ return new Promise((resolve, reject) => { |
+3
-3
@@ -12,3 +12,3 @@ { | ||
| ], | ||
| "version": "1.0.3", | ||
| "version": "1.0.4", | ||
| "private": false, | ||
@@ -37,4 +37,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@fluojs/http": "^1.1.0", | ||
| "@fluojs/runtime": "^1.1.2" | ||
| "@fluojs/http": "^1.1.2", | ||
| "@fluojs/runtime": "^1.1.8" | ||
| }, | ||
@@ -41,0 +41,0 @@ "devDependencies": { |
+5
-5
@@ -30,3 +30,3 @@ # @fluojs/platform-cloudflare-workers | ||
| 이 어댑터는 dispatcher가 binding된 뒤 각 요청 수명주기를 `executionContext.waitUntil(...)`에 연결하고, `close()` 중에도 진행 중인 디스패치를 유지하여 Worker 종료 도중 활성 작업이 중간에 잘리지 않도록 보장합니다. | ||
| 이 어댑터는 dispatcher가 binding된 뒤 각 요청 수명주기를 `executionContext.waitUntil(...)`에 연결하고, `close()` 중에도 진행 중인 디스패치와 SSE(`text/event-stream`) response body를 유지하여 Worker 종료 도중 활성 작업이 중간에 잘리지 않도록 보장합니다. | ||
@@ -91,3 +91,3 @@ 애플리케이션 종료 중에는 즉시 새 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를 받습니다. | ||
| - `fetch()`는 `listen()` 또는 lazy entrypoint가 dispatcher를 binding한 뒤 active work를 `executionContext.waitUntil(...)`에 등록합니다. 그 lifecycle boundary 전에는 upgrade request와 HTTP dispatch가 application handler에 도달하지 않습니다. | ||
| - `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에 도달하지 않습니다. | ||
| - `maxBodySize` 같은 adapter option은 Worker adapter 생성 시 검증됩니다. `globalPrefix`, `cors`, `middleware`, `securityHeaders` 같은 bootstrap 전용 옵션은 `createCloudflareWorkerAdapter(...)`가 아니라 Worker bootstrap helper에 전달해야 합니다. | ||
@@ -97,7 +97,7 @@ - WebSocket upgrade는 HTTP dispatch와 같은 listen boundary가 소유합니다. `listen()` 전의 upgrade request는 설정된 binding에 도달하지 않습니다. | ||
| - Multipart request는 `rawBody`를 보존하지 않습니다. | ||
| - Worker `env` 객체는 fetch entrypoint boundary를 통과하며, package-level config resolution은 application이 소유합니다. | ||
| - Worker `env` 객체는 각 `FrameworkRequest`에 `request.cloudflare.env`로 연결되고 Worker execution context는 `request.cloudflare.executionContext`로 제공됩니다. Package-level config resolution은 application이 소유하므로, binding은 application boundary에서 명시적 provider 또는 `@fluojs/config`로 매핑하세요. | ||
| ## Conformance 커버리지 | ||
| `packages/platform-cloudflare-workers/src/adapter.test.ts`는 문서화된 Worker 계약을 검증하는 package-local regression 대상입니다. 이 파일은 shared Web dispatch delegation, `executionContext.waitUntil(...)` 등록, 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`는 문서화된 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을 검증합니다. | ||
@@ -115,3 +115,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`, `CloudflareWorkerWebSocketBinding`, Worker websocket pair/upgrade type. | ||
| - Option 및 type: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, Worker websocket pair/upgrade type. | ||
@@ -118,0 +118,0 @@ ## 관련 패키지 |
+5
-5
@@ -30,3 +30,3 @@ # @fluojs/platform-cloudflare-workers | ||
| The adapter binds each request lifecycle to `executionContext.waitUntil(...)` after the dispatcher is bound and keeps in-flight dispatches 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 and SSE (`text/event-stream`) response bodies alive during `close()` so Worker shutdown does not drop active work mid-request. | ||
@@ -91,3 +91,3 @@ 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. | ||
| - `fetch()` registers active work with `executionContext.waitUntil(...)` after `listen()` or the lazy entrypoint binds the dispatcher; 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; 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. | ||
| - 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(...)`. | ||
@@ -97,7 +97,7 @@ - WebSocket upgrades are owned by the same listen boundary as HTTP dispatch; upgrade requests before `listen()` do not reach the configured binding. | ||
| - Multipart requests do not preserve `rawBody`. | ||
| - The Worker `env` object is passed through the fetch entrypoint boundary; package-level config resolution remains application-owned. | ||
| - 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. | ||
| ## 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, `executionContext.waitUntil(...)` registration, 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` 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. | ||
@@ -115,3 +115,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`, `CloudflareWorkerWebSocketBinding`, and Worker websocket pair/upgrade types. | ||
| - Options and types: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, and Worker websocket pair/upgrade types. | ||
@@ -118,0 +118,0 @@ ## Related Packages |
41149
9.79%517
18.58%Updated
Updated