@fluojs/platform-cloudflare-workers 1.0.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fluo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,103 @@
1
+ # @fluojs/platform-cloudflare-workers
2
+
3
+ <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
+
5
+ 엣지에 최적화된 fluo 런타임용 Cloudflare Workers HTTP 어댑터 패키지입니다.
6
+
7
+ ## 목차
8
+
9
+ - [설치](#설치)
10
+ - [사용 시점](#사용-시점)
11
+ - [빠른 시작](#빠른-시작)
12
+ - [주요 패턴](#주요-패턴)
13
+ - [공개 API 개요](#공개-api-개요)
14
+ - [관련 패키지](#관련-패키지)
15
+ - [예제 소스](#예제-소스)
16
+
17
+ ## 설치
18
+
19
+ ```bash
20
+ npm install @fluojs/platform-cloudflare-workers
21
+ ```
22
+
23
+ 이 패키지는 Cloudflare Workers에서 실행하는 것을 전제로 합니다. 배포 manifest는 npm 메타데이터가 Workers 런타임 계약과 어긋나지 않도록 의도적으로 `engines.node`를 선언하지 않으며, 저장소의 Node.js 20+ 요구사항은 메인테이너용 빌드/테스트 툴체인에만 적용됩니다.
24
+
25
+ ## 사용 시점
26
+
27
+ fluo 애플리케이션을 [Cloudflare Workers](https://workers.cloudflare.com/)에 배포할 때 이 패키지를 사용합니다. 이 어댑터는 서버리스 엣지 환경에 맞게 설계되었으며, Worker isolate 제약 조건과 네이티브 Web API를 준수하는 가벼운 `fetch` 기반 어댑터를 제공합니다.
28
+
29
+ 이 어댑터는 각 요청 수명주기를 `executionContext.waitUntil(...)`에 연결하고, `close()` 중에도 진행 중인 디스패치를 유지하여 Worker 종료 도중 활성 작업이 중간에 잘리지 않도록 보장합니다.
30
+
31
+ 애플리케이션 종료 중에는 즉시 새 ingress 수락을 중단하고, 활성 HTTP 핸들러가 정리될 수 있도록 최대 10초의 bounded drain window를 제공합니다. 이 시간을 넘기면 `close()`는 무기한 대기하지 않고 timeout 오류로 종료됩니다.
32
+
33
+ ## 빠른 시작
34
+
35
+ ### 표준 어댑터 사용
36
+ 애플리케이션을 부트스트랩하고 표준 Cloudflare Worker `fetch` 핸들러를 내보냅니다.
37
+
38
+ ```typescript
39
+ import { fluoFactory } from '@fluojs/runtime';
40
+ import { createCloudflareWorkerAdapter } from '@fluojs/platform-cloudflare-workers';
41
+ import { AppModule } from './app.module';
42
+
43
+ const adapter = createCloudflareWorkerAdapter();
44
+ const app = await fluoFactory.create(AppModule, { adapter });
45
+
46
+ await app.listen();
47
+
48
+ export default {
49
+ fetch: (req, env, ctx) => adapter.fetch(req, env, ctx),
50
+ };
51
+ ```
52
+
53
+ ### 지연 엔트리포인트 (Zero-Config)
54
+ 첫 번째 요청 시 부트스트랩을 수행하는 엔트리포인트 헬퍼를 사용하여 설정을 더욱 간소화할 수 있습니다.
55
+
56
+ ```typescript
57
+ import { createCloudflareWorkerEntrypoint } from '@fluojs/platform-cloudflare-workers';
58
+ import { AppModule } from './app.module';
59
+
60
+ const worker = createCloudflareWorkerEntrypoint(AppModule);
61
+
62
+ export default {
63
+ fetch: worker.fetch,
64
+ };
65
+ ```
66
+
67
+ ## 주요 패턴
68
+
69
+ ### WebSocketPair 활용
70
+ 어댑터는 `@fluojs/websockets/cloudflare-workers` 바인딩을 통해 실시간 통신을 위한 Cloudflare의 네이티브 `WebSocketPair`를 지원합니다.
71
+
72
+ ```typescript
73
+ @WebSocketGateway({ path: '/ws' })
74
+ export class MyGateway {}
75
+ ```
76
+
77
+ ### 엣지 네이티브 미들웨어
78
+ 표준 fluo 미들웨어(CORS, Global Prefix 등)가 완전히 지원되며 Cloudflare 환경에 최적화되어 있습니다.
79
+
80
+ ```typescript
81
+ const adapter = createCloudflareWorkerAdapter({
82
+ globalPrefix: 'api/v1',
83
+ cors: true,
84
+ });
85
+ ```
86
+
87
+ ## 공개 API 개요
88
+
89
+ - `createCloudflareWorkerAdapter(options)`: Worker HTTP 어댑터를 위한 팩토리입니다.
90
+ - `createCloudflareWorkerEntrypoint(module, options)`: 지연 부트스트랩 방식의 Worker 엔트리포인트를 생성합니다.
91
+ - `bootstrapCloudflareWorkerApplication(module, options)`: Worker를 위한 비동기 부트스트랩 헬퍼입니다.
92
+ - `CloudflareWorkerHttpApplicationAdapter`: 핵심 어댑터 구현 클래스입니다.
93
+
94
+ ## 관련 패키지
95
+
96
+ - `@fluojs/runtime`: 핵심 런타임입니다.
97
+ - `@fluojs/websockets`: 전용 서브패스 `@fluojs/websockets/cloudflare-workers`를 포함합니다.
98
+ - `@fluojs/http`: 공통 HTTP 데코레이터 계층입니다.
99
+
100
+ ## 예제 소스
101
+
102
+ - `packages/platform-cloudflare-workers/src/adapter.test.ts`
103
+ - `packages/websockets/src/cloudflare-workers/cloudflare-workers.test.ts`
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @fluojs/platform-cloudflare-workers
2
+
3
+ <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
+
5
+ Cloudflare Workers HTTP adapter for the fluo runtime, optimized for the edge.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [When to Use](#when-to-use)
11
+ - [Quick Start](#quick-start)
12
+ - [Common Patterns](#common-patterns)
13
+ - [Public API Overview](#public-api-overview)
14
+ - [Related Packages](#related-packages)
15
+ - [Example Sources](#example-sources)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @fluojs/platform-cloudflare-workers
21
+ ```
22
+
23
+ This package is intended to run on Cloudflare Workers. The published manifest intentionally does not declare `engines.node`, so npm metadata stays aligned with the Workers runtime contract; the repository's Node.js 20+ requirement only applies to the maintainer build/test toolchain.
24
+
25
+ ## When to Use
26
+
27
+ 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.
28
+
29
+ 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
+
31
+ 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.
32
+
33
+ ## Quick Start
34
+
35
+ ### Standard Adapter Usage
36
+ Bootstrap your application and export a standard Cloudflare Worker `fetch` handler.
37
+
38
+ ```typescript
39
+ import { fluoFactory } from '@fluojs/runtime';
40
+ import { createCloudflareWorkerAdapter } from '@fluojs/platform-cloudflare-workers';
41
+ import { AppModule } from './app.module';
42
+
43
+ const adapter = createCloudflareWorkerAdapter();
44
+ const app = await fluoFactory.create(AppModule, { adapter });
45
+
46
+ await app.listen();
47
+
48
+ export default {
49
+ fetch: (req, env, ctx) => adapter.fetch(req, env, ctx),
50
+ };
51
+ ```
52
+
53
+ ### Lazy Entrypoint (Zero-Config)
54
+ Use the entrypoint helper for an even simpler setup that bootstraps on the first request.
55
+
56
+ ```typescript
57
+ import { createCloudflareWorkerEntrypoint } from '@fluojs/platform-cloudflare-workers';
58
+ import { AppModule } from './app.module';
59
+
60
+ const worker = createCloudflareWorkerEntrypoint(AppModule);
61
+
62
+ export default {
63
+ fetch: worker.fetch,
64
+ };
65
+ ```
66
+
67
+ ## Common Patterns
68
+
69
+ ### Working with WebSocketPairs
70
+ The adapter supports Cloudflare's native `WebSocketPair` for real-time communication via the `@fluojs/websockets/cloudflare-workers` binding.
71
+
72
+ ```typescript
73
+ @WebSocketGateway({ path: '/ws' })
74
+ export class MyGateway {}
75
+ ```
76
+
77
+ ### Edge-Native Middleware
78
+ Standard fluo middleware (CORS, Global Prefix, etc.) is fully supported and optimized for the Cloudflare environment.
79
+
80
+ ```typescript
81
+ const adapter = createCloudflareWorkerAdapter({
82
+ globalPrefix: 'api/v1',
83
+ cors: true,
84
+ });
85
+ ```
86
+
87
+ ## Public API Overview
88
+
89
+ - `createCloudflareWorkerAdapter(options)`: Factory for the Worker HTTP adapter.
90
+ - `createCloudflareWorkerEntrypoint(module, options)`: Creates a lazy-bootstrapping Worker entrypoint.
91
+ - `bootstrapCloudflareWorkerApplication(module, options)`: Async bootstrap helper for Workers.
92
+ - `CloudflareWorkerHttpApplicationAdapter`: The core adapter implementation.
93
+
94
+ ## Related Packages
95
+
96
+ - `@fluojs/runtime`: Core framework runtime.
97
+ - `@fluojs/websockets`: Includes specific subpath `@fluojs/websockets/cloudflare-workers`.
98
+ - `@fluojs/http`: Shared HTTP decorators.
99
+
100
+ ## Example Sources
101
+
102
+ - `packages/platform-cloudflare-workers/src/adapter.test.ts`
103
+ - `packages/websockets/src/cloudflare-workers/cloudflare-workers.test.ts`
@@ -0,0 +1,120 @@
1
+ import { type Dispatcher, type HttpApplicationAdapter } from '@fluojs/http';
2
+ import { type BootstrapHttpAdapterApplicationOptions } from '@fluojs/runtime/internal/http-adapter';
3
+ import type { Application, ModuleType, UploadedFile } from '@fluojs/runtime';
4
+ import { type CreateWebRequestResponseFactoryOptions } from '@fluojs/runtime/web';
5
+ declare module '@fluojs/http' {
6
+ interface FrameworkRequest {
7
+ files?: UploadedFile[];
8
+ rawBody?: Uint8Array;
9
+ }
10
+ }
11
+ /** Minimal Worker execution context surface used by the adapter. */
12
+ export interface CloudflareWorkerExecutionContext {
13
+ passThroughOnException?(): void;
14
+ waitUntil(promise: Promise<unknown>): void;
15
+ }
16
+ /** Message payloads accepted by Cloudflare Worker websockets. */
17
+ export type CloudflareWorkerWebSocketMessage = ArrayBuffer | ArrayBufferView | Blob | string;
18
+ /** Server-side Cloudflare Worker websocket shape used by the raw binding seam. */
19
+ export interface CloudflareWorkerWebSocket extends Pick<WebSocket, 'addEventListener' | 'close' | 'removeEventListener' | 'send'> {
20
+ readonly readyState: number;
21
+ accept(): void;
22
+ }
23
+ /** Pair returned by Cloudflare's `WebSocketPair` constructor. */
24
+ export interface CloudflareWorkerWebSocketPair {
25
+ 0: CloudflareWorkerWebSocket;
26
+ 1: CloudflareWorkerWebSocket;
27
+ }
28
+ /** Factory for creating Cloudflare Worker websocket pairs during upgrades. */
29
+ export type CloudflareWorkerWebSocketPairFactory = () => CloudflareWorkerWebSocketPair;
30
+ /** Result returned when the adapter upgrades a request to a Worker websocket. */
31
+ export interface CloudflareWorkerWebSocketUpgradeResult {
32
+ response: Response;
33
+ serverSocket: CloudflareWorkerWebSocket;
34
+ }
35
+ /** Host wrapper passed to websocket bindings for performing Worker upgrades. */
36
+ export interface CloudflareWorkerWebSocketUpgradeHost {
37
+ upgrade(request: Request): CloudflareWorkerWebSocketUpgradeResult;
38
+ }
39
+ /** Official websocket binding contract consumed by `@fluojs/websockets/cloudflare-workers`. */
40
+ export interface CloudflareWorkerWebSocketBinding {
41
+ fetch(request: Request, host: CloudflareWorkerWebSocketUpgradeHost): Response | Promise<Response>;
42
+ }
43
+ /** Hook surface exposed by the Worker adapter for websocket bindings. */
44
+ export interface CloudflareWorkerWebSocketBindingHost {
45
+ configureWebSocketBinding(binding: CloudflareWorkerWebSocketBinding | undefined): void;
46
+ }
47
+ /** Parsing and transport options for the Cloudflare Worker adapter. */
48
+ export interface CloudflareWorkerAdapterOptions extends CreateWebRequestResponseFactoryOptions {
49
+ createWebSocketPair?: CloudflareWorkerWebSocketPairFactory;
50
+ }
51
+ /** Bootstrap options for constructing a Cloudflare Worker application shell. */
52
+ export interface BootstrapCloudflareWorkerApplicationOptions extends BootstrapHttpAdapterApplicationOptions, CloudflareWorkerAdapterOptions {
53
+ }
54
+ /** Fetch handler shape exposed by Worker-backed application entrypoints. */
55
+ export interface CloudflareWorkerHandler<Env = unknown> {
56
+ fetch(request: Request, env: Env, executionContext: CloudflareWorkerExecutionContext): Promise<Response>;
57
+ }
58
+ /** Fully bootstrapped Cloudflare Worker application wrapper. */
59
+ export interface CloudflareWorkerApplication<Env = unknown> extends CloudflareWorkerHandler<Env> {
60
+ readonly adapter: CloudflareWorkerHttpApplicationAdapter;
61
+ readonly app: Application;
62
+ close(signal?: string): Promise<void>;
63
+ }
64
+ /** Lazy Cloudflare Worker entrypoint that bootstraps on first use. */
65
+ export interface CloudflareWorkerEntrypoint<Env = unknown> extends CloudflareWorkerHandler<Env> {
66
+ close(signal?: string): Promise<void>;
67
+ ready(): Promise<CloudflareWorkerApplication<Env>>;
68
+ }
69
+ /**
70
+ * Cloudflare Workers HTTP adapter with waitUntil-aware request tracking and graceful close behavior.
71
+ */
72
+ export declare class CloudflareWorkerHttpApplicationAdapter implements HttpApplicationAdapter, CloudflareWorkerWebSocketBindingHost {
73
+ private readonly options;
74
+ private closeInFlight?;
75
+ private dispatcher?;
76
+ private inFlightDrain?;
77
+ private inFlightRequestCount;
78
+ private websocketBinding?;
79
+ constructor(options?: CloudflareWorkerAdapterOptions);
80
+ close(): Promise<void>;
81
+ getRealtimeCapability(): import("@fluojs/http").FetchStyleHttpAdapterRealtimeCapability;
82
+ configureWebSocketBinding(binding: CloudflareWorkerWebSocketBinding | undefined): void;
83
+ fetch<Env = unknown>(request: Request, _env?: Env, executionContext?: CloudflareWorkerExecutionContext): Promise<Response>;
84
+ listen(dispatcher: Dispatcher): Promise<void>;
85
+ private upgradeWebSocket;
86
+ private trackInFlightRequest;
87
+ private waitForInFlightRequests;
88
+ }
89
+ /**
90
+ * Create the canonical Cloudflare Worker adapter instance.
91
+ *
92
+ * @param options Parsing, raw-body, and websocket-pair options for Worker requests.
93
+ * @returns A Cloudflare Worker HTTP adapter.
94
+ */
95
+ export declare function createCloudflareWorkerAdapter(options?: CloudflareWorkerAdapterOptions): CloudflareWorkerHttpApplicationAdapter;
96
+ /**
97
+ * Bootstrap a Cloudflare Worker application and return its fetch-capable wrapper.
98
+ *
99
+ * @param rootModule Root module compiled by the Fluo runtime.
100
+ * @param options Worker adapter and runtime bootstrap options.
101
+ * @returns A bootstrapped Worker application wrapper with `fetch(...)` and `close(...)`.
102
+ */
103
+ export declare function bootstrapCloudflareWorkerApplication<Env = unknown>(rootModule: ModuleType, options?: BootstrapCloudflareWorkerApplicationOptions): Promise<CloudflareWorkerApplication<Env>>;
104
+ /**
105
+ * Create a lazy Cloudflare Worker entrypoint that bootstraps once on first request.
106
+ *
107
+ * @param rootModule Root module compiled by the Fluo runtime.
108
+ * @param options Worker adapter and runtime bootstrap options.
109
+ * @returns A Worker entrypoint exposing lazy `fetch(...)`, `ready()`, and `close(...)` helpers.
110
+ */
111
+ export declare function createCloudflareWorkerEntrypoint<Env = unknown>(rootModule: ModuleType, options?: BootstrapCloudflareWorkerApplicationOptions): CloudflareWorkerEntrypoint<Env>;
112
+ declare global {
113
+ interface ResponseInit {
114
+ webSocket?: CloudflareWorkerWebSocket;
115
+ }
116
+ interface GlobalThis {
117
+ WebSocketPair?: new () => CloudflareWorkerWebSocketPair;
118
+ }
119
+ }
120
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,313 @@
1
+ import { createFetchStyleHttpAdapterRealtimeCapability } from '@fluojs/http';
2
+ import { bootstrapHttpAdapterApplication } from '@fluojs/runtime/internal/http-adapter';
3
+ import { dispatchWebRequest } from '@fluojs/runtime/web';
4
+ const WORKER_DISPATCHER_NOT_READY_MESSAGE = 'Cloudflare Workers adapter received a request before dispatcher binding completed.';
5
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
6
+
7
+ /** Minimal Worker execution context surface used by the adapter. */
8
+
9
+ /** Message payloads accepted by Cloudflare Worker websockets. */
10
+
11
+ /** Server-side Cloudflare Worker websocket shape used by the raw binding seam. */
12
+
13
+ /** Pair returned by Cloudflare's `WebSocketPair` constructor. */
14
+
15
+ /** Factory for creating Cloudflare Worker websocket pairs during upgrades. */
16
+
17
+ /** Result returned when the adapter upgrades a request to a Worker websocket. */
18
+
19
+ /** Host wrapper passed to websocket bindings for performing Worker upgrades. */
20
+
21
+ /** Official websocket binding contract consumed by `@fluojs/websockets/cloudflare-workers`. */
22
+
23
+ /** Hook surface exposed by the Worker adapter for websocket bindings. */
24
+
25
+ /** Parsing and transport options for the Cloudflare Worker adapter. */
26
+
27
+ /** Bootstrap options for constructing a Cloudflare Worker application shell. */
28
+
29
+ /** Fetch handler shape exposed by Worker-backed application entrypoints. */
30
+
31
+ /** Fully bootstrapped Cloudflare Worker application wrapper. */
32
+
33
+ /** Lazy Cloudflare Worker entrypoint that bootstraps on first use. */
34
+
35
+ /**
36
+ * Cloudflare Workers HTTP adapter with waitUntil-aware request tracking and graceful close behavior.
37
+ */
38
+ export class CloudflareWorkerHttpApplicationAdapter {
39
+ closeInFlight;
40
+ dispatcher;
41
+ inFlightDrain;
42
+ inFlightRequestCount = 0;
43
+ websocketBinding;
44
+ constructor(options = {}) {
45
+ this.options = options;
46
+ }
47
+ async close() {
48
+ if (this.closeInFlight) {
49
+ await waitForCloseWithTimeout(this.closeInFlight, DEFAULT_SHUTDOWN_TIMEOUT_MS);
50
+ return;
51
+ }
52
+ if (!this.dispatcher) {
53
+ return;
54
+ }
55
+ const closeInFlight = this.waitForInFlightRequests().finally(() => {
56
+ this.closeInFlight = undefined;
57
+ this.dispatcher = undefined;
58
+ });
59
+ this.closeInFlight = closeInFlight;
60
+ void closeInFlight.catch(() => {});
61
+ await waitForCloseWithTimeout(closeInFlight, DEFAULT_SHUTDOWN_TIMEOUT_MS);
62
+ }
63
+ getRealtimeCapability() {
64
+ return createFetchStyleHttpAdapterRealtimeCapability('Cloudflare Workers exposes WebSocketPair isolate-local request-upgrade hosting. Use @fluojs/websockets/cloudflare-workers for the official raw websocket binding.', {
65
+ support: 'supported'
66
+ });
67
+ }
68
+ configureWebSocketBinding(binding) {
69
+ this.websocketBinding = binding;
70
+ }
71
+ async fetch(request, _env, executionContext) {
72
+ if (this.closeInFlight) {
73
+ return createShutdownResponse();
74
+ }
75
+ const release = this.trackInFlightRequest();
76
+ const responsePromise = (async () => {
77
+ try {
78
+ if (this.websocketBinding && isWebSocketUpgradeRequest(request)) {
79
+ return await this.websocketBinding.fetch(request, {
80
+ upgrade: upgradeRequest => this.upgradeWebSocket(upgradeRequest)
81
+ });
82
+ }
83
+ return await dispatchWebRequest({
84
+ ...this.options,
85
+ dispatcher: this.dispatcher,
86
+ dispatcherNotReadyMessage: WORKER_DISPATCHER_NOT_READY_MESSAGE,
87
+ request
88
+ });
89
+ } finally {
90
+ release();
91
+ }
92
+ })();
93
+ executionContext?.waitUntil(responsePromise.then(() => undefined, () => undefined));
94
+ return await responsePromise;
95
+ }
96
+ async listen(dispatcher) {
97
+ this.dispatcher = dispatcher;
98
+ }
99
+ upgradeWebSocket(_request) {
100
+ const pair = resolveWebSocketPairFactory(this.options.createWebSocketPair)();
101
+ const clientSocket = pair[0];
102
+ const serverSocket = pair[1];
103
+ return {
104
+ response: createWebSocketUpgradeResponse(clientSocket),
105
+ serverSocket
106
+ };
107
+ }
108
+ trackInFlightRequest() {
109
+ this.inFlightRequestCount += 1;
110
+ if (this.inFlightRequestCount === 1) {
111
+ this.inFlightDrain = createDeferred();
112
+ }
113
+ return () => {
114
+ if (this.inFlightRequestCount === 0) {
115
+ return;
116
+ }
117
+ this.inFlightRequestCount -= 1;
118
+ if (this.inFlightRequestCount === 0) {
119
+ this.inFlightDrain?.resolve();
120
+ this.inFlightDrain = undefined;
121
+ }
122
+ };
123
+ }
124
+ async waitForInFlightRequests() {
125
+ if (this.inFlightRequestCount === 0) {
126
+ return;
127
+ }
128
+ await this.inFlightDrain?.promise;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Create the canonical Cloudflare Worker adapter instance.
134
+ *
135
+ * @param options Parsing, raw-body, and websocket-pair options for Worker requests.
136
+ * @returns A Cloudflare Worker HTTP adapter.
137
+ */
138
+ export function createCloudflareWorkerAdapter(options = {}) {
139
+ return new CloudflareWorkerHttpApplicationAdapter(options);
140
+ }
141
+
142
+ /**
143
+ * Bootstrap a Cloudflare Worker application and return its fetch-capable wrapper.
144
+ *
145
+ * @param rootModule Root module compiled by the Fluo runtime.
146
+ * @param options Worker adapter and runtime bootstrap options.
147
+ * @returns A bootstrapped Worker application wrapper with `fetch(...)` and `close(...)`.
148
+ */
149
+ export async function bootstrapCloudflareWorkerApplication(rootModule, options = {}) {
150
+ const adapter = createCloudflareWorkerAdapter(options);
151
+ const app = await bootstrapHttpAdapterApplication(rootModule, options, adapter);
152
+ await app.listen();
153
+ return {
154
+ adapter,
155
+ app,
156
+ close(signal) {
157
+ return app.close(signal);
158
+ },
159
+ fetch(request, env, executionContext) {
160
+ return adapter.fetch(request, env, executionContext);
161
+ }
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Create a lazy Cloudflare Worker entrypoint that bootstraps once on first request.
167
+ *
168
+ * @param rootModule Root module compiled by the Fluo runtime.
169
+ * @param options Worker adapter and runtime bootstrap options.
170
+ * @returns A Worker entrypoint exposing lazy `fetch(...)`, `ready()`, and `close(...)` helpers.
171
+ */
172
+ export function createCloudflareWorkerEntrypoint(rootModule, options = {}) {
173
+ let closeError;
174
+ let closeInFlight;
175
+ let runningApplication;
176
+ const ready = async () => {
177
+ if (closeError) {
178
+ throw closeError;
179
+ }
180
+ if (!runningApplication) {
181
+ runningApplication = bootstrapCloudflareWorkerApplication(rootModule, options);
182
+ }
183
+ return await runningApplication;
184
+ };
185
+ return {
186
+ async close(signal) {
187
+ if (closeInFlight) {
188
+ await closeInFlight;
189
+ return;
190
+ }
191
+ if (closeError) {
192
+ throw closeError;
193
+ }
194
+ const application = runningApplication;
195
+ if (!application) {
196
+ return;
197
+ }
198
+ const closing = (async () => {
199
+ try {
200
+ await (await application).close(signal);
201
+ if (runningApplication === application) {
202
+ runningApplication = undefined;
203
+ }
204
+ } catch (error) {
205
+ closeError = error;
206
+ throw error;
207
+ } finally {
208
+ closeInFlight = undefined;
209
+ }
210
+ })();
211
+ closeInFlight = closing;
212
+ await closing;
213
+ },
214
+ async fetch(request, env, executionContext) {
215
+ if (closeError || closeInFlight) {
216
+ return createShutdownResponse();
217
+ }
218
+ return await (await ready()).fetch(request, env, executionContext);
219
+ },
220
+ ready
221
+ };
222
+ }
223
+ function createWebSocketUpgradeResponse(socket) {
224
+ try {
225
+ return new Response(null, {
226
+ status: 101,
227
+ webSocket: socket
228
+ });
229
+ } catch {
230
+ const response = Object.create(Response.prototype);
231
+ Object.defineProperties(response, {
232
+ headers: {
233
+ value: new Headers()
234
+ },
235
+ ok: {
236
+ value: false
237
+ },
238
+ redirected: {
239
+ value: false
240
+ },
241
+ status: {
242
+ value: 101
243
+ },
244
+ statusText: {
245
+ value: 'Switching Protocols'
246
+ },
247
+ type: {
248
+ value: 'default'
249
+ },
250
+ url: {
251
+ value: ''
252
+ },
253
+ webSocket: {
254
+ value: socket
255
+ }
256
+ });
257
+ return response;
258
+ }
259
+ }
260
+ function resolveWebSocketPairFactory(createWebSocketPair) {
261
+ if (createWebSocketPair) {
262
+ return createWebSocketPair;
263
+ }
264
+ const pair = globalThis.WebSocketPair;
265
+ if (typeof pair === 'function') {
266
+ return () => new pair();
267
+ }
268
+ throw new Error('Cloudflare Workers websocket support requires globalThis.WebSocketPair or options.createWebSocketPair().');
269
+ }
270
+ function isWebSocketUpgradeRequest(request) {
271
+ return request.headers.get('upgrade')?.toLowerCase() === 'websocket';
272
+ }
273
+ function createDeferred() {
274
+ let resolve;
275
+ let reject;
276
+ const promise = new Promise((res, rej) => {
277
+ resolve = res;
278
+ reject = rej;
279
+ });
280
+ return {
281
+ promise,
282
+ reject,
283
+ resolve
284
+ };
285
+ }
286
+ function createShutdownResponse() {
287
+ return new Response(JSON.stringify({
288
+ error: {
289
+ code: 'SERVICE_UNAVAILABLE',
290
+ message: 'Server is shutting down.',
291
+ status: 503
292
+ }
293
+ }), {
294
+ headers: {
295
+ 'content-type': 'application/json'
296
+ },
297
+ status: 503
298
+ });
299
+ }
300
+ function waitForCloseWithTimeout(closePromise, timeoutMs) {
301
+ return new Promise((resolve, reject) => {
302
+ const timeoutHandle = setTimeout(() => {
303
+ reject(new Error(`Cloudflare Workers adapter shutdown timeout exceeded ${String(timeoutMs)}ms.`));
304
+ }, timeoutMs);
305
+ void closePromise.then(() => {
306
+ clearTimeout(timeoutHandle);
307
+ resolve();
308
+ }, error => {
309
+ clearTimeout(timeoutHandle);
310
+ reject(error);
311
+ });
312
+ });
313
+ }
@@ -0,0 +1,2 @@
1
+ export * from './adapter.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './adapter.js';
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@fluojs/platform-cloudflare-workers",
3
+ "description": "Cloudflare Workers HTTP adapter for the Fluo runtime built on the shared Web Request/Response core.",
4
+ "keywords": [
5
+ "fluo",
6
+ "cloudflare",
7
+ "workers",
8
+ "http-adapter",
9
+ "platform",
10
+ "fetch"
11
+ ],
12
+ "version": "1.0.0-beta.1",
13
+ "private": false,
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/fluojs/fluo.git",
18
+ "directory": "packages/platform-cloudflare-workers"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "dependencies": {
36
+ "@fluojs/http": "^1.0.0-beta.1",
37
+ "@fluojs/runtime": "^1.0.0-beta.1"
38
+ },
39
+ "devDependencies": {
40
+ "vitest": "^3.2.4"
41
+ },
42
+ "scripts": {
43
+ "prebuild": "node ../../tooling/scripts/clean-dist.mjs",
44
+ "build": "pnpm exec babel src --extensions .ts --ignore 'src/**/*.test.ts' --out-dir dist --config-file ../../tooling/babel/babel.config.cjs && pnpm exec tsc -p tsconfig.build.json",
45
+ "typecheck": "pnpm exec tsc -p tsconfig.json --noEmit",
46
+ "test": "pnpm exec vitest run -c vitest.config.ts",
47
+ "test:watch": "pnpm exec vitest -c vitest.config.ts"
48
+ }
49
+ }