@fluojs/platform-cloudflare-workers 1.0.2 → 1.0.4

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/README.ko.md CHANGED
@@ -27,7 +27,7 @@ npm install @fluojs/platform-cloudflare-workers
27
27
 
28
28
  fluo 애플리케이션을 [Cloudflare Workers](https://workers.cloudflare.com/)에 배포할 때 이 패키지를 사용합니다. 이 어댑터는 서버리스 엣지 환경에 맞게 설계되었으며, Worker isolate 제약 조건과 네이티브 Web API를 준수하는 가벼운 `fetch` 기반 어댑터를 제공합니다.
29
29
 
30
- 이 어댑터는 각 요청 수명주기를 `executionContext.waitUntil(...)`에 연결하고, `close()` 중에도 진행 중인 디스패치를 유지하여 Worker 종료 도중 활성 작업이 중간에 잘리지 않도록 보장합니다.
30
+ 이 어댑터는 dispatcher가 binding된 뒤 각 요청 수명주기를 `executionContext.waitUntil(...)`에 연결하고, `close()` 중에도 진행 중인 디스패치와 SSE(`text/event-stream`) response body를 유지하여 Worker 종료 도중 활성 작업이 중간에 잘리지 않도록 보장합니다.
31
31
 
32
32
  애플리케이션 종료 중에는 즉시 새 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를 받습니다.
33
33
 
@@ -76,10 +76,10 @@ export class MyGateway {}
76
76
  ```
77
77
 
78
78
  ### 엣지 네이티브 미들웨어
79
- 표준 fluo 미들웨어(CORS, Global Prefix 등) 완전히 지원되며 Cloudflare 환경에 최적화되어 있습니다.
79
+ 표준 fluo 미들웨어(CORS, Global Prefix 등) Worker bootstrap helper를 통해 완전히 지원되며 Cloudflare 환경에 최적화되어 있습니다. `createCloudflareWorkerAdapter(...)`는 adapter가 소유하는 parsing 및 websocket-pair 옵션만 받습니다. Routing 및 middleware 옵션은 `bootstrapCloudflareWorkerApplication(...)` 또는 `createCloudflareWorkerEntrypoint(...)`에 전달하세요.
80
80
 
81
81
  ```typescript
82
- const adapter = createCloudflareWorkerAdapter({
82
+ const worker = createCloudflareWorkerEntrypoint(AppModule, {
83
83
  globalPrefix: 'api/v1',
84
84
  cors: true,
85
85
  });
@@ -87,15 +87,16 @@ const adapter = createCloudflareWorkerAdapter({
87
87
 
88
88
  ### 동작 참고
89
89
 
90
- - `fetch()`는 `listen()`이 dispatcher를 binding한 뒤 active work를 `executionContext.waitUntil(...)`에 등록합니다.
90
+ - `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에 도달하지 않습니다.
91
+ - `maxBodySize` 같은 adapter option은 Worker adapter 생성 시 검증됩니다. `globalPrefix`, `cors`, `middleware`, `securityHeaders` 같은 bootstrap 전용 옵션은 `createCloudflareWorkerAdapter(...)`가 아니라 Worker bootstrap helper에 전달해야 합니다.
91
92
  - WebSocket upgrade는 HTTP dispatch와 같은 listen boundary가 소유합니다. `listen()` 전의 upgrade request는 설정된 binding에 도달하지 않습니다.
92
93
  - `close()`는 shutdown 중 및 shutdown 이후 새 요청에 JSON `503` response를 반환하고, active request가 끝나지 않으면 10초 뒤 timeout됩니다. 해당 close drain이 아직 활성 상태일 때 `listen()`을 호출하면 Cloudflare Workers adapter shutdown-draining 오류로 reject됩니다.
93
94
  - Multipart request는 `rawBody`를 보존하지 않습니다.
94
- - Worker `env` 객체는 fetch entrypoint boundary를 통과하며, package-level config resolution은 application이 소유합니다.
95
+ - Worker `env` 객체는 `FrameworkRequest`에 `request.cloudflare.env`로 연결되고 Worker execution context는 `request.cloudflare.executionContext`로 제공됩니다. Package-level config resolution은 application이 소유하므로, binding은 application boundary에서 명시적 provider 또는 `@fluojs/config`로 매핑하세요.
95
96
 
96
97
  ## Conformance 커버리지
97
98
 
98
- `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을 검증합니다.
99
+ `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을 검증합니다.
99
100
 
100
101
  공유 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와 계속 동기화되도록 확인합니다.
101
102
 
@@ -108,7 +109,7 @@ const adapter = createCloudflareWorkerAdapter({
108
109
  - `CloudflareWorkerHandler`: Worker application wrapper와 lazy entrypoint가 공유하는 fetch handler interface입니다.
109
110
  - `CloudflareWorkerApplication`: `adapter`, `app`, `fetch(...)`, `close(...)`를 제공하는 fully bootstrapped Worker application wrapper입니다.
110
111
  - `CloudflareWorkerEntrypoint`: `fetch`, `ready()`, `close()` lifecycle method를 제공하는 lazy entrypoint입니다.
111
- - Option 및 type: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerWebSocketBinding`, Worker websocket pair/upgrade type.
112
+ - Option 및 type: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, Worker websocket pair/upgrade type.
112
113
 
113
114
  ## 관련 패키지
114
115
 
package/README.md CHANGED
@@ -27,7 +27,7 @@ This package is intended to run on Cloudflare Workers. The published manifest in
27
27
 
28
28
  Use this package when deploying fluo applications to [Cloudflare Workers](https://workers.cloudflare.com/). It is designed for the serverless edge environment, providing a lightweight `fetch`-based adapter that respects Worker isolate constraints and native Web APIs.
29
29
 
30
- The adapter binds each request lifecycle to `executionContext.waitUntil(...)` and keeps in-flight dispatches alive during `close()` so Worker shutdown does not drop active work mid-request.
30
+ 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.
31
31
 
32
32
  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.
33
33
 
@@ -76,10 +76,10 @@ export class MyGateway {}
76
76
  ```
77
77
 
78
78
  ### Edge-Native Middleware
79
- Standard fluo middleware (CORS, Global Prefix, etc.) is fully supported and optimized for the Cloudflare environment.
79
+ 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.
80
80
 
81
81
  ```typescript
82
- const adapter = createCloudflareWorkerAdapter({
82
+ const worker = createCloudflareWorkerEntrypoint(AppModule, {
83
83
  globalPrefix: 'api/v1',
84
84
  cors: true,
85
85
  });
@@ -87,15 +87,16 @@ const adapter = createCloudflareWorkerAdapter({
87
87
 
88
88
  ### Behavior Notes
89
89
 
90
- - `fetch()` registers active work with `executionContext.waitUntil(...)` after `listen()` binds the dispatcher.
90
+ - `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.
91
+ - 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(...)`.
91
92
  - WebSocket upgrades are owned by the same listen boundary as HTTP dispatch; upgrade requests before `listen()` do not reach the configured binding.
92
93
  - `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.
93
94
  - Multipart requests do not preserve `rawBody`.
94
- - The Worker `env` object is passed through the fetch entrypoint boundary; package-level config resolution remains application-owned.
95
+ - 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.
95
96
 
96
97
  ## Conformance Coverage
97
98
 
98
- `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.
99
+ `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.
99
100
 
100
101
  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.
101
102
 
@@ -108,7 +109,7 @@ The shared edge portability suite in `packages/testing/src/portability/web-runti
108
109
  - `CloudflareWorkerHandler`: Fetch handler interface shared by Worker application wrappers and lazy entrypoints.
109
110
  - `CloudflareWorkerApplication`: Fully bootstrapped Worker application wrapper with `adapter`, `app`, `fetch(...)`, and `close(...)`.
110
111
  - `CloudflareWorkerEntrypoint`: Lazy entrypoint with `fetch`, `ready()`, and `close()` lifecycle methods.
111
- - Options and types: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerWebSocketBinding`, and Worker websocket pair/upgrade types.
112
+ - Options and types: `CloudflareWorkerAdapterOptions`, `BootstrapCloudflareWorkerApplicationOptions`, `CloudflareWorkerExecutionContext`, `CloudflareWorkerRequestContext`, `CloudflareWorkerWebSocketBinding`, and Worker websocket pair/upgrade types.
112
113
 
113
114
  ## Related Packages
114
115
 
package/dist/adapter.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { Application, ModuleType, UploadedFile } from '@fluojs/runtime';
4
4
  import { type CreateWebRequestResponseFactoryOptions } from '@fluojs/runtime/web';
5
5
  declare module '@fluojs/http' {
6
6
  interface FrameworkRequest {
7
+ cloudflare?: CloudflareWorkerRequestContext;
7
8
  files?: UploadedFile[];
8
9
  rawBody?: Uint8Array;
9
10
  }
@@ -13,6 +14,11 @@ export interface CloudflareWorkerExecutionContext {
13
14
  passThroughOnException?(): void;
14
15
  waitUntil(promise: Promise<unknown>): void;
15
16
  }
17
+ /** Worker-specific request context attached to fluo HTTP requests by the Cloudflare adapter. */
18
+ export interface CloudflareWorkerRequestContext<Env = unknown> {
19
+ readonly env: Env;
20
+ readonly executionContext?: CloudflareWorkerExecutionContext;
21
+ }
16
22
  /** Message payloads accepted by Cloudflare Worker websockets. */
17
23
  export type CloudflareWorkerWebSocketMessage = ArrayBuffer | ArrayBufferView | Blob | string;
18
24
  /** Server-side Cloudflare Worker websocket shape used by the raw binding seam. */
@@ -82,11 +88,12 @@ export declare class CloudflareWorkerHttpApplicationAdapter implements HttpAppli
82
88
  close(): Promise<void>;
83
89
  getRealtimeCapability(): import("@fluojs/http").FetchStyleHttpAdapterRealtimeCapability;
84
90
  configureWebSocketBinding(binding: CloudflareWorkerWebSocketBinding | undefined): void;
85
- fetch<Env = unknown>(request: Request, _env?: Env, executionContext?: CloudflareWorkerExecutionContext): Promise<Response>;
91
+ fetch<Env = unknown>(request: Request, env?: Env, executionContext?: CloudflareWorkerExecutionContext): Promise<Response>;
86
92
  listen(dispatcher: Dispatcher): Promise<void>;
87
93
  private upgradeWebSocket;
88
94
  private trackInFlightRequest;
89
95
  private waitForInFlightRequests;
96
+ private createRequestResponseFactory;
90
97
  }
91
98
  /**
92
99
  * Create the canonical Cloudflare Worker adapter instance.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
package/dist/adapter.js CHANGED
@@ -6,6 +6,8 @@ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
6
6
 
7
7
  /** Minimal Worker execution context surface used by the adapter. */
8
8
 
9
+ /** Worker-specific request context attached to fluo HTTP requests by the Cloudflare adapter. */
10
+
9
11
  /** Message payloads accepted by Cloudflare Worker websockets. */
10
12
 
11
13
  /** Server-side Cloudflare Worker websocket shape used by the raw binding seam. */
@@ -45,6 +47,7 @@ export class CloudflareWorkerHttpApplicationAdapter {
45
47
  options;
46
48
  webRequestResponseFactory;
47
49
  constructor(options = {}) {
50
+ validateNonNegativeIntegerOption('maxBodySize', options.maxBodySize);
48
51
  this.options = options;
49
52
  this.webRequestResponseFactory = createWebRequestResponseFactory(options);
50
53
  }
@@ -74,31 +77,39 @@ export class CloudflareWorkerHttpApplicationAdapter {
74
77
  configureWebSocketBinding(binding) {
75
78
  this.websocketBinding = binding;
76
79
  }
77
- async fetch(request, _env, executionContext) {
80
+ async fetch(request, env, executionContext) {
78
81
  if (this.closeInFlight || this.isClosed) {
79
82
  return createShutdownResponse();
80
83
  }
81
84
  const release = this.trackInFlightRequest();
82
- const responsePromise = (async () => {
85
+ const dispatcher = this.dispatcher;
86
+ if (dispatcher && this.websocketBinding && isWebSocketUpgradeRequest(request)) {
83
87
  try {
84
- const dispatcher = this.dispatcher;
85
- if (dispatcher && this.websocketBinding && isWebSocketUpgradeRequest(request)) {
86
- return await this.websocketBinding.fetch(request, {
87
- upgrade: upgradeRequest => this.upgradeWebSocket(upgradeRequest)
88
- });
89
- }
90
- return await dispatchWebRequest({
91
- dispatcher,
92
- dispatcherNotReadyMessage: WORKER_DISPATCHER_NOT_READY_MESSAGE,
93
- factory: this.webRequestResponseFactory,
94
- request
88
+ const response = await this.websocketBinding.fetch(request, {
89
+ upgrade: upgradeRequest => this.upgradeWebSocket(upgradeRequest)
95
90
  });
91
+ executionContext?.waitUntil(Promise.resolve());
92
+ return response;
96
93
  } finally {
97
94
  release();
98
95
  }
96
+ }
97
+ const responsePromise = (async () => {
98
+ return await dispatchWebRequest({
99
+ dispatcher,
100
+ dispatcherNotReadyMessage: WORKER_DISPATCHER_NOT_READY_MESSAGE,
101
+ factory: this.createRequestResponseFactory(env, executionContext),
102
+ request
103
+ });
99
104
  })();
100
- executionContext?.waitUntil(responsePromise.then(() => undefined, () => undefined));
101
- return await responsePromise;
105
+ const trackedResponsePromise = responsePromise.then(response => createLifecycleTrackedResponse(response, release), error => {
106
+ release();
107
+ throw error;
108
+ });
109
+ executionContext?.waitUntil(trackedResponsePromise.then(({
110
+ lifecycle
111
+ }) => lifecycle).then(() => undefined, () => undefined));
112
+ return (await trackedResponsePromise).response;
102
113
  }
103
114
  async listen(dispatcher) {
104
115
  if (this.closeInFlight) {
@@ -138,6 +149,20 @@ export class CloudflareWorkerHttpApplicationAdapter {
138
149
  }
139
150
  await this.inFlightDrain?.promise;
140
151
  }
152
+ createRequestResponseFactory(env, executionContext) {
153
+ const baseFactory = this.webRequestResponseFactory;
154
+ return {
155
+ ...baseFactory,
156
+ async createRequest(request, signal) {
157
+ const frameworkRequest = await baseFactory.createRequest(request, signal);
158
+ frameworkRequest.cloudflare = {
159
+ env,
160
+ executionContext
161
+ };
162
+ return frameworkRequest;
163
+ }
164
+ };
165
+ }
141
166
  }
142
167
 
143
168
  /**
@@ -278,6 +303,14 @@ function resolveWebSocketPairFactory(createWebSocketPair) {
278
303
  }
279
304
  throw new Error('Cloudflare Workers websocket support requires globalThis.WebSocketPair or options.createWebSocketPair().');
280
305
  }
306
+ function validateNonNegativeIntegerOption(name, value) {
307
+ if (value === undefined) {
308
+ return;
309
+ }
310
+ if (!Number.isInteger(value) || value < 0) {
311
+ throw new Error(`Invalid ${name} value: ${String(value)}. Expected a non-negative integer.`);
312
+ }
313
+ }
281
314
  function isWebSocketUpgradeRequest(request) {
282
315
  return request.headers.get('upgrade')?.toLowerCase() === 'websocket';
283
316
  }
@@ -308,6 +341,57 @@ function createShutdownResponse() {
308
341
  status: 503
309
342
  });
310
343
  }
344
+ function createLifecycleTrackedResponse(response, release) {
345
+ if (!isLifecycleTrackedStreamingResponse(response)) {
346
+ release();
347
+ return {
348
+ lifecycle: Promise.resolve(),
349
+ response
350
+ };
351
+ }
352
+ const lifecycle = createDeferred();
353
+ const responseBody = response.body;
354
+ if (!responseBody) {
355
+ release();
356
+ return {
357
+ lifecycle: Promise.resolve(),
358
+ response
359
+ };
360
+ }
361
+ const reader = responseBody.getReader();
362
+ const trackedBody = new ReadableStream({
363
+ async cancel(reason) {
364
+ try {
365
+ await reader.cancel(reason);
366
+ lifecycle.resolve();
367
+ } catch (error) {
368
+ lifecycle.reject(error);
369
+ throw error;
370
+ }
371
+ },
372
+ async pull(controller) {
373
+ try {
374
+ const result = await reader.read();
375
+ if (result.done) {
376
+ controller.close();
377
+ lifecycle.resolve();
378
+ return;
379
+ }
380
+ controller.enqueue(result.value);
381
+ } catch (error) {
382
+ controller.error(error);
383
+ lifecycle.reject(error);
384
+ }
385
+ }
386
+ });
387
+ return {
388
+ lifecycle: lifecycle.promise.finally(release),
389
+ response: new Response(trackedBody, response)
390
+ };
391
+ }
392
+ function isLifecycleTrackedStreamingResponse(response) {
393
+ return response.body !== null && response.headers.get('content-type')?.toLowerCase().includes('text/event-stream') === true;
394
+ }
311
395
  function waitForCloseWithTimeout(closePromise, timeoutMs) {
312
396
  return new Promise((resolve, reject) => {
313
397
  const timeoutHandle = setTimeout(() => {
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "platform",
10
10
  "fetch"
11
11
  ],
12
- "version": "1.0.2",
12
+ "version": "1.0.4",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@fluojs/http": "^1.0.0",
37
- "@fluojs/runtime": "^1.1.0"
36
+ "@fluojs/http": "^1.1.2",
37
+ "@fluojs/runtime": "^1.1.8"
38
38
  },
39
39
  "devDependencies": {
40
40
  "vitest": "^3.2.4"