@fluojs/platform-deno 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md CHANGED
@@ -43,8 +43,32 @@ await runDenoApplication(AppModule, {
43
43
  });
44
44
  ```
45
45
 
46
+ Managed startup 경로는 network listener를 열고 기본적으로 `SIGINT`/`SIGTERM` listener를 등록합니다. Network 접근 권한을 부여해 entrypoint를 실행하세요.
47
+
48
+ ```bash
49
+ deno run --allow-net main.ts
50
+ ```
51
+
52
+ Signal listener 등록에는 별도의 Deno permission이 필요하지 않습니다. Adapter 자체는 environment variable을 읽지 않습니다. 애플리케이션 코드가 해당 key를 읽을 때만 `--allow-env=PORT,DATABASE_URL`처럼 범위를 제한한 권한을 추가하세요. Signal로 트리거된 애플리케이션 close 실패는 helper가 log한 뒤 swallow하며 exit status를 설정하지 않습니다. Failure-status propagation 또는 forced termination이 필요한 host는 `runDenoApplication(...)`에 `shutdownSignals: false`를 전달하고 signal과 shutdown을 직접 조율해야 합니다.
53
+
46
54
  ## 주요 패턴
47
55
 
56
+ ### Early Hints 미지원
57
+
58
+ Deno의 Fetch `Response`는 final response 이전 informational response를 표현할 수 없으므로 `context.response.earlyHints`가 없습니다. 사용 전에 capability 존재 여부를 확인하세요. Adapter는 요청된 `103`을 silent no-op 또는 final-response header로 바꾸지 않습니다.
59
+
60
+ ### 스트리밍 멀티파트 소비
61
+
62
+ 애플리케이션 bootstrap에서 `multipart: { strategy: 'stream' }`을 설정하면 멀티파트 데이터를 점진적으로
63
+ 받습니다. 멀티파트 route에서 `RequestContext.request.body`는 `AsyncIterableIterator<MultipartPart>`입니다.
64
+ field part는 `kind: 'field'`, `name`, `value`, `headers`를, file part는 `kind: 'file'`, `name`, `filename`,
65
+ `contentType`, `headers`, 그리고 `stream`의 single-consumer `ReadableStream<Uint8Array>`를 제공합니다. 다음
66
+ part를 요청하기 전에 각 file stream을 끝까지 소비하거나 cancel하세요.
67
+
68
+ Runtime route dispatch는 route를 위해 만든 iterator를 소유하며 handler가 끝난 뒤 자동으로 `return()`을 호출해
69
+ active source를 cancel하고 release합니다. Standalone `parseMultipartStream(...)` consumer는 이 책임을 직접
70
+ 집니다. iterator를 끝까지 소비하거나 일찍 끝낼 때 `return()`을 호출하세요.
71
+
48
72
  ### Host-Owned Deno.serve
49
73
  애플리케이션이 `Deno.serve(...)`를 소유한다면 `app.listen()`을 호출하지 않고 fluo 애플리케이션을 bootstrap한 뒤 public dispatcher로 request handler를 만드세요. `createDenoFetchHandler(...)`는 request 변환과 dispatch만 수행하며 server를 시작하거나 shutdown, signal, websocket upgrade를 소유하지 않습니다.
50
74
 
@@ -68,7 +92,7 @@ try {
68
92
  }
69
93
  ```
70
94
 
71
- 주변 host는 `server` 중지와 process signal 조율을 소유하고 websocket upgrade를 별도로 처리할지 결정해야 합니다. fluo해당 lifecycle seam을 소유해야 한다면 managed `runDenoApplication(...)` 또는 `app.listen()` 경로를 사용하세요. `adapter.handle(...)`은 managed adapter의 `listen(dispatcher)` binding이 끝난 뒤에만 사용할 수 있습니다.
95
+ 주변 host는 `server` 중지와 process signal 조율을 소유하고 websocket upgrade를 별도로 처리할지 결정해야 합니다. Fluomanaged server lifecycle을 소유해야 한다면 `app.listen()`을 사용하고, shutdown signal listener까지 추가로 설치해야 한다면 `runDenoApplication(...)`을 사용하세요. `adapter.handle(...)`은 managed adapter의 `listen(dispatcher)` binding이 끝난 뒤에만 사용할 수 있습니다.
72
96
 
73
97
  ### 직접 어댑터 생성
74
98
  애플리케이션 코드는 보통 `createDenoAdapter(options)`, `bootstrapDenoApplication(...)`, `runDenoApplication(...)`을 사용해 adapter setup 경계를 명확히 유지하는 편을 권장합니다. 커스텀 orchestration이나 테스트에서는 `new DenoHttpApplicationAdapter(options?)`를 직접 사용할 수 있으며, constructor는 factory와 같은 optional public `DenoAdapterOptions`를 받고 options가 생략되면 기본 port를 적용하며, portable `host` alias보다 `hostname`을 우선하고, 잘못된 `port` 또는 `maxBodySize` 값은 setup 시점에 거절합니다.
@@ -96,6 +120,14 @@ export class MyGateway {
96
120
  export class RealtimeModule {}
97
121
  ```
98
122
 
123
+ #### Deno websocket 수신 payload
124
+
125
+ `DenoWebSocketMessage`는 Deno binding이 전달할 수 있는 모든 payload form,
126
+ `ArrayBuffer | ArrayBufferView | Blob | string`을 나타냅니다. 기존에 이 public
127
+ union을 `Blob | string`으로 exhaustive narrowing한 handler는 `ArrayBuffer`와
128
+ `ArrayBufferView` branch를 추가해야 합니다. `@fluojs/websockets/deno` binding은
129
+ 이미 gateway handler를 dispatch하기 전에 이 binary payload를 수용하고 normalize합니다.
130
+
99
131
  ## HTTPS와 런타임 이식성
100
132
 
101
133
  `https` 옵션으로 Deno TLS 인증서 자료를 전달하면 `Deno.serve`를 HTTPS 모드로 시작할 수 있습니다. 어댑터는 `https.cert`와 `https.key`를 Deno의 `cert` 및 `key`로 전달하며, 시작 로그도 `https://` listen URL을 보고하므로 Deno 패키지가 공유 HTTP 어댑터 이식성 계약과 정렬됩니다.
@@ -113,13 +145,15 @@ await runDenoApplication(AppModule, {
113
145
 
114
146
  `hostname`은 Deno 네이티브 옵션 이름으로 유지됩니다. 공유 HTTP 어댑터 테스트와 교차 런타임 설정 헬퍼를 위해 `host`도 이식성 alias로 허용하며, 둘 다 제공하면 `Deno.serve(...)` bind target과 보고되는 listen URL에는 `hostname`이 우선합니다.
115
147
 
116
- Advanced option에는 test 또는 non-hosted runtime을 위한 injectable `serve`, `upgradeWebSocket` seam, `rawBody`, `maxBodySize`, `multipart`, `shutdownSignals`가 포함됩니다. `createDenoFetchHandler(...)`는 같은 request parsing option을 받으며 byte-exact JSON/text raw body를 보존하고 multipart request에서는 `rawBody`를 제외합니다. Seam을 주입하지 않으면 managed adapter는 listen/upgrade 시점에 `globalThis.Deno.serve`와 `globalThis.Deno.upgradeWebSocket`으로 fallback합니다. `runDenoApplication(...)`은 기본적으로 `SIGINT`/`SIGTERM`을 연결하고, `shutdownSignals: false`는 signal registration을 끄며, 여러 signal을 등록하다 실패하면 이미 연결한 listener를 rollback합니다. 이미 실행 중인 adapter에 대한 중복 `listen(...)` 호출은 원래 dispatcher pipeline을 보존하는 no-op입니다. Close는 Deno serve signal을 abort하기 전에 active request drain을 최대 10초 기다립니다. `handle(...)`은 websocket upgrade 요청을 포함해 `listen()`이 dispatcher를 bind하기 전에는 JSON `500`, shutdown 진행 중에는 JSON `503`을 반환합니다.
148
+ Advanced option에는 test 또는 non-hosted runtime을 위한 injectable `serve`, `upgradeWebSocket` seam, `rawBody`, `maxBodySize`, `multipart`, `shutdownSignals`가 포함됩니다. `createDenoFetchHandler(...)`는 같은 request parsing option을 받으며 byte-exact JSON/text raw body를 보존하고 multipart request에서는 `rawBody`를 제외합니다. Seam을 주입하지 않으면 managed adapter는 listen/upgrade 시점에 `globalThis.Deno.serve`와 `globalThis.Deno.upgradeWebSocket`으로 fallback합니다. `runDenoApplication(...)`은 기본적으로 `SIGINT`/`SIGTERM`을 연결하고, `shutdownSignals: false`는 signal registration을 끄며, 여러 signal을 등록하다 실패하면 이미 연결한 listener를 rollback합니다. 이미 실행 중인 adapter에 대한 중복 `listen(...)` 호출은 원래 dispatcher pipeline을 보존하는 no-op입니다. Close는 Deno serve signal을 abort하기 전에 active request drain을 최대 10초 기다립니다. Graceful shutdown이 reject되면 adapter는 ingress를 abort하지만 `server.finished`가 settle될 때까지 active server controller를 유지하고, 그 뒤에만 managed lifecycle을 해제한 다음 원래 shutdown error를 다시 throw합니다. `handle(...)`은 websocket upgrade 요청을 포함해 `listen()`이 dispatcher를 bind하기 전에는 JSON `500`, shutdown 진행 중에는 JSON `503`을 반환합니다.
117
149
 
118
150
  ## Conformance 커버리지
119
151
 
120
- `packages/platform-deno/src/adapter.test.ts`는 managed Deno 계약을 검증하는 package-local regression 대상입니다. 이 파일은 shared Web dispatch delegation, `listen(dispatcher)` 이후 direct `adapter.handle(...)` success-path dispatch, 직접 constructor/factory option normalization, HTTPS startup forwarding, `Deno.serve(...)` bind target과 startup log에 대한 `host` alias 및 `hostname` 우선순위, 중복 `listen(...)` no-op dispatcher 보존, 기본 `SIGINT`/`SIGTERM` signal listener 등록, `shutdownSignals: false`, partial signal-registration failure 이후 listener rollback, websocket upgrade binding 및 no-binding HTTP fallback, websocket listen 전 bootstrap gating, global Deno serve/upgrade fallback seam, listen 전 `500` 처리, shutdown 중 `503` 처리, serve-signal abort 전 in-flight request drain, bounded 10초 close timeout을 검증합니다. `packages/platform-deno/src/fetch-handler.test.ts`는 host-owned handler에 shared web-runtime portability harness를 적용하여 cookie/query decoding, JSON/text와 byte-exact raw body, multipart exclusion, SSE framing, dispatch가 `Deno.serve(...)`를 호출하지 않는다는 사실을 검증합니다. `packages/platform-deno/src/declaration-surface.test.ts`는 package를 다시 build하고 manifest가 export하는 declaration을 검증합니다.
152
+ `packages/platform-deno/src/adapter.test.ts`는 managed Deno 계약을 검증하는 package-local regression 대상입니다. 이 파일은 shared Web dispatch delegation, `listen(dispatcher)` 이후 direct `adapter.handle(...)` success-path dispatch, 직접 constructor/factory option normalization, HTTPS startup forwarding, `Deno.serve(...)` bind target과 startup log에 대한 `host` alias 및 `hostname` 우선순위, 중복 `listen(...)` no-op dispatcher 보존, 기본 `SIGINT`/`SIGTERM` signal listener 등록, `shutdownSignals: false`, partial signal-registration failure 이후 listener rollback, websocket upgrade binding 및 no-binding HTTP fallback, websocket listen 전 bootstrap gating, global Deno serve/upgrade fallback seam, listen 전 `500` 처리, shutdown 중 `503` 처리, serve-signal abort 전 in-flight request drain, `server.finished`까지 shutdown failure ownership 유지, bounded 10초 close timeout을 검증합니다. `packages/platform-deno/src/fetch-handler.test.ts`는 host-owned handler에 shared web-runtime portability harness를 적용하여 cookie/query decoding, JSON/text와 byte-exact raw body, multipart exclusion, SSE framing, dispatch가 `Deno.serve(...)`를 호출하지 않는다는 사실을 검증합니다. `packages/platform-deno/src/declaration-surface.test.ts`는 package를 다시 build하고 manifest가 export하는 declaration을 검증합니다.
153
+
154
+ 공유 edge portability suite인 `packages/testing/src/portability/web-runtime-adapter-portability.test.ts`는 Deno를 Bun 및 Cloudflare Workers와 함께 실행해 malformed cookie 보존, query decoding, JSON/text raw-body capture, 단일 byte-range status/header/body semantic, multipart raw-body 제외, SSE framing을 검증합니다. 패키지 테스트의 README parity assertion은 이 edge-runtime 커버리지 문서가 한국어 mirror와 계속 동기화되도록 확인합니다.
121
155
 
122
- 공유 edge portability suite인 `packages/testing/src/portability/web-runtime-adapter-portability.test.ts`는 DenoBun Cloudflare Workers와 함께 실행해 malformed cookie 보존, query decoding, JSON/text raw-body capture, multipart raw-body 제외, SSE framing을 검증합니다. 패키지 테스트의 README parity assertion은edge-runtime 커버리지 문서가 한국어 mirror와 계속 동기화되도록 확인합니다.
156
+ Deno 2 smoke lane은 public `npm:@fluojs/platform-deno` root import검사하고 built adapter closure를 네이티브로 실행합니다. Listener 없이 host-owned `createDenoFetchHandler(...)` request를 dispatch한 뒤, port `0`과 `shutdownSignals: false`로 `runDenoApplication(...)`을 시작하고 실제 route를 fetch한 다음 애플리케이션을 닫습니다.managed-listener test에서는 signal ownership을 의도적으로 비활성화하며, signal registration은 별도의 package-local contract suite에서 다룹니다.
123
157
 
124
158
  ## 공개 API 개요
125
159
 
@@ -129,12 +163,13 @@ Advanced option에는 test 또는 non-hosted runtime을 위한 injectable `serve
129
163
  - `runDenoApplication(module, options)`: Deno를 위한 권장 빠른 시작 헬퍼입니다.
130
164
  - `DenoHttpApplicationAdapter(options?)`: 핵심 adapter 구현체입니다. Direct `new DenoHttpApplicationAdapter()` 또는 `new DenoHttpApplicationAdapter(options)`는 `createDenoAdapter(options)`와 같은 기본 port, `host` alias 처리, `hostname` 우선순위, 숫자 option validation을 적용합니다.
131
165
  - `listen(dispatcher)`: fluo HTTP dispatcher를 bind하고 `Deno.serve`를 시작합니다. 중복 호출은 원래 dispatcher를 보존하는 no-op입니다.
132
- - `close()`: 새 유입을 중단하고 active request를 최대 10초 drain한 뒤, shutdown이 끝나지 않으면 Deno serve signal을 abort합니다.
166
+ - `close()`: 새 유입을 중단하고 active request를 최대 10초 drain한 뒤, shutdown이 끝나지 않으면 Deno serve signal을 abort합니다. Graceful shutdown이 reject되면 `server.finished`가 settle될 때까지 `getServer()` ownership을 유지하고, 이후 managed lifecycle을 해제한 다음 원래 shutdown error를 다시 throw합니다.
133
167
  - `handle(request)`: 수동 `Request` to `Response` 디스패처입니다. `listen(dispatcher)`가 runtime dispatcher를 bind한 뒤에는 성공 경로를 실행하고, bind 전에는 JSON `500`, shutdown 중에는 JSON `503`을 반환합니다.
134
168
  - `getListenTarget()`: Deno `hostname` 또는 portable `host` alias를 사용해 bind target과 public URL을 보고합니다.
135
169
  - `getRealtimeCapability()`: runtime integration을 위한 fetch-style Deno websocket upgrade capability를 보고합니다.
136
170
  - `getServer()`: adapter가 listen 중일 때 active `Deno.serve` controller를 반환합니다.
137
171
  - `configureWebSocketBinding(...)`: `listen(dispatcher)`가 server를 시작하기 전에 `@fluojs/websockets/deno` binding을 설치합니다.
172
+ - `DenoWebSocketMessage`: 전체 수신 websocket payload union인 `ArrayBuffer | ArrayBufferView | Blob | string`입니다.
138
173
  - `https: { cert, key }`: `Deno.serve`로 전달되고 보고되는 listen URL에 반영되는 HTTPS 시작 옵션입니다.
139
174
  - Option 및 seam type: `CreateDenoFetchHandlerOptions`, `DenoServeOptions`, `DenoServeController`, `DenoServerWebSocket`, websocket binding interface, bootstrap/run option, listen-target helper.
140
175
 
package/README.md CHANGED
@@ -43,8 +43,32 @@ await runDenoApplication(AppModule, {
43
43
  });
44
44
  ```
45
45
 
46
+ The managed startup path opens a network listener and registers `SIGINT`/`SIGTERM` listeners by default. Run the entrypoint with network access:
47
+
48
+ ```bash
49
+ deno run --allow-net main.ts
50
+ ```
51
+
52
+ Signal listener registration does not require a separate Deno permission. The adapter does not read environment variables. Add a scoped grant such as `--allow-env=PORT,DATABASE_URL` only when application code reads those keys. Signal-triggered application-close failures are logged and swallowed by the helper; it does not set an exit status. Hosts that require failure-status propagation or forced termination must pass `shutdownSignals: false` to `runDenoApplication(...)` and coordinate signals and shutdown themselves.
53
+
46
54
  ## Common Patterns
47
55
 
56
+ ### Early Hints are unsupported
57
+
58
+ Deno's Fetch `Response` cannot represent an informational response before the final response, so `context.response.earlyHints` is absent. Check for capability presence before use. The adapter never turns a requested `103` into a silent no-op or a final-response header.
59
+
60
+ ### Streaming multipart consumption
61
+
62
+ Set `multipart: { strategy: 'stream' }` at application bootstrap to receive multipart data incrementally. For
63
+ multipart routes, `RequestContext.request.body` is an `AsyncIterableIterator<MultipartPart>`: field parts expose
64
+ `kind: 'field'`, `name`, `value`, and `headers`; file parts expose `kind: 'file'`, `name`, `filename`,
65
+ `contentType`, `headers`, and a single-consumer `ReadableStream<Uint8Array>` at `stream`. Finish or cancel each file
66
+ stream before requesting the next part.
67
+
68
+ Runtime route dispatch owns an iterator created for a route and automatically calls `return()` after the handler
69
+ finishes, cancelling and releasing an active source. Standalone `parseMultipartStream(...)` consumers own that
70
+ responsibility: consume the iterator to completion or call `return()` when ending early.
71
+
48
72
  ### Host-Owned Deno.serve
49
73
  If your application owns `Deno.serve(...)`, bootstrap the fluo application without calling `app.listen()` and create a request handler from its public dispatcher. `createDenoFetchHandler(...)` only translates and dispatches requests; it never starts a server or owns shutdown, signals, or websocket upgrades.
50
74
 
@@ -68,7 +92,7 @@ try {
68
92
  }
69
93
  ```
70
94
 
71
- The surrounding host must stop `server`, coordinate process signals, and decide whether websocket upgrades are handled separately. Use the managed `runDenoApplication(...)` or `app.listen()` path when fluo should own those lifecycle seams. `adapter.handle(...)` remains available only after the managed adapter's `listen(dispatcher)` binding has completed.
95
+ The surrounding host must stop `server`, coordinate process signals, and decide whether websocket upgrades are handled separately. Use `app.listen()` when fluo should own the managed server lifecycle, or `runDenoApplication(...)` when fluo should additionally install shutdown signal listeners. `adapter.handle(...)` remains available only after the managed adapter's `listen(dispatcher)` binding has completed.
72
96
 
73
97
  ### Direct Adapter Construction
74
98
  Application code should usually prefer `createDenoAdapter(options)`, `bootstrapDenoApplication(...)`, or `runDenoApplication(...)` so adapter setup stays explicit. Custom orchestration and tests may use `new DenoHttpApplicationAdapter(options?)` directly; the constructor accepts the same optional public `DenoAdapterOptions` as the factory, applies the default port when options are omitted, preserves `hostname` over the portable `host` alias, and rejects invalid `port` or `maxBodySize` values during setup.
@@ -96,6 +120,14 @@ export class MyGateway {
96
120
  export class RealtimeModule {}
97
121
  ```
98
122
 
123
+ #### Deno websocket inbound payloads
124
+
125
+ `DenoWebSocketMessage` represents every payload form the Deno binding can deliver:
126
+ `ArrayBuffer | ArrayBufferView | Blob | string`. Existing handlers that narrowed this
127
+ public union exhaustively for `Blob | string` must add `ArrayBuffer` and
128
+ `ArrayBufferView` branches. The `@fluojs/websockets/deno` binding already accepts and
129
+ normalizes these binary payloads before it dispatches gateway handlers.
130
+
99
131
  ## HTTPS and Runtime Portability
100
132
 
101
133
  Pass Deno TLS certificate material through the `https` option to start `Deno.serve` in HTTPS mode. The adapter forwards `https.cert` and `https.key` to Deno as `cert` and `key`, and startup logging reports an `https://` listen URL so the Deno package stays aligned with the shared HTTP adapter portability contract.
@@ -113,13 +145,15 @@ await runDenoApplication(AppModule, {
113
145
 
114
146
  `hostname` remains the Deno-native option name. The adapter also accepts `host` as a portability alias for shared HTTP adapter tests and cross-runtime configuration helpers; when both are provided, `hostname` wins for the `Deno.serve(...)` bind target and reported listen URL.
115
147
 
116
- Advanced options include injectable `serve` and `upgradeWebSocket` seams for tests or non-hosted runtimes, `rawBody`, `maxBodySize`, `multipart`, and `shutdownSignals`. `createDenoFetchHandler(...)` accepts the same request parsing options and preserves byte-exact JSON/text raw bodies while excluding `rawBody` for multipart requests. When a seam is not injected, the managed adapter falls back to `globalThis.Deno.serve` and `globalThis.Deno.upgradeWebSocket` at listen/upgrade time. `runDenoApplication(...)` wires `SIGINT`/`SIGTERM` by default, `shutdownSignals: false` disables signal registration, and failed multi-signal registration rolls back listeners that were already attached. Duplicate `listen(...)` calls on an already-running adapter are no-ops that preserve the original dispatcher pipeline. Close waits up to 10 seconds for active requests to drain before aborting the Deno serve signal. `handle(...)` returns a JSON `500` before `listen()` binds the dispatcher, including websocket upgrade requests, and a JSON `503` while shutdown is in progress.
148
+ Advanced options include injectable `serve` and `upgradeWebSocket` seams for tests or non-hosted runtimes, `rawBody`, `maxBodySize`, `multipart`, and `shutdownSignals`. `createDenoFetchHandler(...)` accepts the same request parsing options and preserves byte-exact JSON/text raw bodies while excluding `rawBody` for multipart requests. When a seam is not injected, the managed adapter falls back to `globalThis.Deno.serve` and `globalThis.Deno.upgradeWebSocket` at listen/upgrade time. `runDenoApplication(...)` wires `SIGINT`/`SIGTERM` by default, `shutdownSignals: false` disables signal registration, and failed multi-signal registration rolls back listeners that were already attached. Duplicate `listen(...)` calls on an already-running adapter are no-ops that preserve the original dispatcher pipeline. Close waits up to 10 seconds for active requests to drain before aborting the Deno serve signal. If graceful shutdown rejects, the adapter aborts ingress but retains the active server controller until `server.finished` settles; only then does it release the managed lifecycle and rethrow the original shutdown error. `handle(...)` returns a JSON `500` before `listen()` binds the dispatcher, including websocket upgrade requests, and a JSON `503` while shutdown is in progress.
117
149
 
118
150
  ## Conformance Coverage
119
151
 
120
- `packages/platform-deno/src/adapter.test.ts` is the package-local regression target for the managed Deno contract. It covers shared Web dispatch delegation, direct `adapter.handle(...)` success-path dispatch after `listen(dispatcher)`, direct constructor/factory option normalization, HTTPS startup forwarding, `host` alias and `hostname` precedence for the `Deno.serve(...)` bind target and startup log, duplicate `listen(...)` no-op dispatcher preservation, default `SIGINT`/`SIGTERM` signal listener registration, `shutdownSignals: false`, listener rollback after partial signal-registration failure, websocket upgrade binding and no-binding HTTP fallback, websocket pre-listen bootstrap gating, global Deno serve/upgrade fallback seams, pre-listen `500` handling, shutdown `503` handling, in-flight request drain before serve-signal abort, and the bounded 10-second close timeout. `packages/platform-deno/src/fetch-handler.test.ts` applies the shared web-runtime portability harness to the host-owned handler, covering cookies/query decoding, JSON/text and byte-exact raw bodies, multipart exclusion, SSE framing, and proof that dispatch does not call `Deno.serve(...)`. `packages/platform-deno/src/declaration-surface.test.ts` rebuilds the package and verifies the manifest-exported declarations.
152
+ `packages/platform-deno/src/adapter.test.ts` is the package-local regression target for the managed Deno contract. It covers shared Web dispatch delegation, direct `adapter.handle(...)` success-path dispatch after `listen(dispatcher)`, direct constructor/factory option normalization, HTTPS startup forwarding, `host` alias and `hostname` precedence for the `Deno.serve(...)` bind target and startup log, duplicate `listen(...)` no-op dispatcher preservation, default `SIGINT`/`SIGTERM` signal listener registration, `shutdownSignals: false`, listener rollback after partial signal-registration failure, websocket upgrade binding and no-binding HTTP fallback, websocket pre-listen bootstrap gating, global Deno serve/upgrade fallback seams, pre-listen `500` handling, shutdown `503` handling, in-flight request drain before serve-signal abort, shutdown-failure ownership until `server.finished`, and the bounded 10-second close timeout. `packages/platform-deno/src/fetch-handler.test.ts` applies the shared web-runtime portability harness to the host-owned handler, covering cookies/query decoding, JSON/text and byte-exact raw bodies, multipart exclusion, SSE framing, and proof that dispatch does not call `Deno.serve(...)`. `packages/platform-deno/src/declaration-surface.test.ts` rebuilds the package and verifies the manifest-exported declarations.
153
+
154
+ The shared edge portability suite in `packages/testing/src/portability/web-runtime-adapter-portability.test.ts` exercises Deno beside Bun and Cloudflare Workers for malformed cookie preservation, query decoding, JSON/text raw-body capture, single byte-range status/header/body semantics, 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.
121
155
 
122
- The shared edge portability suite in `packages/testing/src/portability/web-runtime-adapter-portability.test.ts` exercises Deno beside Bun and Cloudflare Workers 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.
156
+ The Deno 2 smoke lane checks the public `npm:@fluojs/platform-deno` root import and executes the built adapter closure natively. It dispatches a host-owned `createDenoFetchHandler(...)` request without a listener, then starts `runDenoApplication(...)` on port `0` with `shutdownSignals: false`, fetches a real route, and closes the application. Signal ownership remains disabled for that managed-listener test; signal registration is covered separately by the package-local contract suite.
123
157
 
124
158
  ## Public API Overview
125
159
 
@@ -129,12 +163,13 @@ The shared edge portability suite in `packages/testing/src/portability/web-runti
129
163
  - `runDenoApplication(module, options)`: Recommended quick-start helper for Deno.
130
164
  - `DenoHttpApplicationAdapter(options?)`: Core adapter implementation. Direct `new DenoHttpApplicationAdapter()` or `new DenoHttpApplicationAdapter(options)` applies the same default port, `host` alias handling, `hostname` precedence, and numeric option validation as `createDenoAdapter(options)`.
131
165
  - `listen(dispatcher)`: Binds the fluo HTTP dispatcher and starts `Deno.serve`; duplicate calls are no-ops while preserving the original dispatcher.
132
- - `close()`: Stops ingress, drains active requests for up to 10 seconds, and aborts the Deno serve signal if shutdown does not settle.
166
+ - `close()`: Stops ingress, drains active requests for up to 10 seconds, and aborts the Deno serve signal if shutdown does not settle. A rejected graceful shutdown retains `getServer()` ownership until `server.finished` settles, then releases the managed lifecycle and rethrows the original shutdown error.
133
167
  - `handle(request)`: Manual `Request` to `Response` dispatcher. It succeeds after `listen(dispatcher)` binds the runtime dispatcher, returns JSON `500` before binding, and returns JSON `503` while shutdown is in progress.
134
168
  - `getListenTarget()`: Reports the bind target and public URL using Deno `hostname` or the portable `host` alias.
135
169
  - `getRealtimeCapability()`: Reports the fetch-style Deno websocket upgrade capability for runtime integration.
136
170
  - `getServer()`: Returns the active `Deno.serve` controller while the adapter is listening.
137
171
  - `configureWebSocketBinding(...)`: Installs the `@fluojs/websockets/deno` binding before `listen(dispatcher)` starts the server.
172
+ - `DenoWebSocketMessage`: The full inbound websocket payload union: `ArrayBuffer | ArrayBufferView | Blob | string`.
138
173
  - `https: { cert, key }`: HTTPS startup options forwarded to `Deno.serve` and reflected in the reported listen URL.
139
174
  - Option and seam types: `CreateDenoFetchHandlerOptions`, `DenoServeOptions`, `DenoServeController`, `DenoServerWebSocket`, websocket binding interfaces, bootstrap/run options, and listen-target helpers.
140
175
 
package/dist/adapter.d.ts CHANGED
@@ -23,7 +23,7 @@ export interface DenoServeController {
23
23
  /** Deno shutdown signals supported by `runDenoApplication(...)`. */
24
24
  export type DenoApplicationSignal = 'SIGINT' | 'SIGTERM';
25
25
  /** Message payloads delivered through Deno server websocket bindings. */
26
- export type DenoWebSocketMessage = Blob | string;
26
+ export type DenoWebSocketMessage = ArrayBuffer | ArrayBufferView | Blob | string;
27
27
  /** Server-side websocket shape used by the Deno platform binding seam. */
28
28
  export interface DenoServerWebSocket extends Pick<WebSocket, 'addEventListener' | 'close' | 'removeEventListener' | 'send'> {
29
29
  readonly readyState: number;
@@ -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":"AAAA,OAAO,EAAiD,KAAK,UAAU,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpI,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACpG,OAAO,EACL,KAAK,sCAAsC,EAG3C,KAAK,uBAAuB,EAC5B,KAAK,gCAAgC,EAEtC,MAAM,uCAAuC,CAAC;AAS/C,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClC;AAED,oEAAoE;AACpE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzD,yEAAyE;AACzE,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,MAAM,CAAC;AAEjF,0EAA0E;AAC1E,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,SAAS,EAAE,kBAAkB,GAAG,OAAO,GAAG,qBAAqB,GAAG,MAAM,CAAC;IACzH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,kFAAkF;AAClF,MAAM,WAAW,0BAA0B,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB;IACnG,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,0EAA0E;AAC1E,MAAM,MAAM,4BAA4B,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB,IAAI,CACpG,OAAO,EAAE,OAAO,KACb,0BAA0B,CAAC,OAAO,CAAC,CAAC;AAEzC,mFAAmF;AACnF,MAAM,WAAW,wBAAwB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB;IACjG,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;CAChE;AAED,sFAAsF;AACtF,MAAM,WAAW,oBAAoB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB;IAC7F,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,wBAAwB,CAAC,OAAO,CAAC,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAChG;AAED,4EAA4E;AAC5E,MAAM,WAAW,wBAAwB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB;IACjG,yBAAyB,CAAC,OAAO,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;CACrF;AAED,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAClF,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,gBAAgB,KACtB,mBAAmB,CAAC;AAEzB,KAAK,cAAc,GAAG;IACpB,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACjF,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpF,KAAK,EAAE,iBAAiB,CAAC;IACzB,gBAAgB,EAAE,4BAA4B,CAAC;CAChD,CAAC;AAEF,0FAA0F;AAC1F,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,gBAAgB,CAAC,EAAE,4BAA4B,CAAC;CACjD;AAED,gFAAgF;AAChF,MAAM,WAAW,+BAAgC,SAAQ,sCAAsC,EAAE,kBAAkB;IACjH,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,0EAA0E;AAC1E,MAAM,WAAW,yBAA0B,SAAQ,gCAAgC,EAAE,kBAAkB;IACrG,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,qBAAqB,EAAE,CAAC;CAC5D;AAMD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB,IAAI,CAAC,EAAE,cAAc,CAAC;KACvB;CACF;AAED;;;;;GAKG;AAEH,qBAAa,0BAA2B,YAAW,sBAAsB;IACvE,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,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,aAAa,CAAC,CAAwB;IAC9C,OAAO,CAAC,MAAM,CAAC,CAAsB;IACrC,OAAO,CAAC,gBAAgB,CAAC,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+E;IACvG,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IAE3C;;;;OAIG;gBACS,OAAO,GAAE,kBAAuB;IAW5C,SAAS,IAAI,mBAAmB,GAAG,SAAS;IAI5C,eAAe,IAAI,uBAAuB;IAQ1C,qBAAqB;IAOrB,yBAAyB,CAAC,OAAO,SAAS,mBAAmB,EAC3D,OAAO,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,SAAS,GACjD,IAAI;IAQD,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IA6C3C,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC5B,OAAO,CAAC,oBAAoB;YAqBd,uBAAuB;CAOtC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,0BAA0B,CAE9F;AAcD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,WAAW,CAAC,CAItB;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,WAAW,CAAC,CAStB"}
package/dist/adapter.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createFetchStyleHttpAdapterRealtimeCapability } from '@fluojs/http/internal';
2
2
  import { bootstrapHttpAdapterApplication, createDefaultApplicationLogger, runHttpAdapterApplication } from '@fluojs/runtime/internal/http-adapter';
3
- import { createWebRequestResponseFactory, dispatchWebRequest } from '@fluojs/runtime/web';
3
+ import { createWebRequestResponseFactory, startWebRequestDispatch } from '@fluojs/runtime/web';
4
4
  import { validateNonNegativeIntegerOption } from './options.js';
5
5
 
6
6
  /** Listen target metadata reported by `Deno.serve(...)` callbacks. */
@@ -95,14 +95,18 @@ export class DenoHttpApplicationAdapter {
95
95
  return createShutdownResponse();
96
96
  }
97
97
  const release = this.trackInFlightRequest();
98
+ let releaseAfterResponse = true;
98
99
  try {
99
100
  if (!this.dispatcher) {
100
- return await dispatchWebRequest({
101
+ const dispatch = startWebRequestDispatch({
101
102
  dispatcher: this.dispatcher,
102
103
  dispatcherNotReadyMessage: 'Deno adapter received a request before dispatcher binding completed.',
103
104
  factory: this.webRequestResponseFactory,
104
105
  request
105
106
  });
107
+ releaseAfterResponse = false;
108
+ void dispatch.completion.finally(release).catch(() => {});
109
+ return await dispatch.response;
106
110
  }
107
111
  if (this.websocketBinding && isWebSocketUpgradeRequest(request)) {
108
112
  const upgradeWebSocket = resolveUpgradeWebSocket(this.options.upgradeWebSocket);
@@ -110,40 +114,56 @@ export class DenoHttpApplicationAdapter {
110
114
  upgrade: upgradeRequest => upgradeWebSocket(upgradeRequest)
111
115
  });
112
116
  }
113
- return await dispatchWebRequest({
117
+ const dispatch = startWebRequestDispatch({
114
118
  dispatcher: this.dispatcher,
115
119
  dispatcherNotReadyMessage: 'Deno adapter received a request before dispatcher binding completed.',
116
120
  factory: this.webRequestResponseFactory,
117
121
  request
118
122
  });
123
+ releaseAfterResponse = false;
124
+ void dispatch.completion.finally(release).catch(() => {});
125
+ return await dispatch.response;
119
126
  } finally {
120
- release();
127
+ if (releaseAfterResponse) {
128
+ release();
129
+ }
121
130
  }
122
131
  }
123
132
  async listen(dispatcher) {
124
133
  if (this.server) {
125
134
  return;
126
135
  }
127
- this.dispatcher = dispatcher;
136
+ const previousAbortController = this.abortController;
137
+ const previousDispatcher = this.dispatcher;
138
+ const previousListenAddress = this.listenAddress;
128
139
  const abortController = new AbortController();
129
- const serve = resolveServe(this.options.serve);
130
140
  const listenReady = this.options.port === 0 ? createDeferred() : undefined;
131
- this.abortController = abortController;
132
- this.server = serve({
133
- cert: this.options.https?.cert,
134
- hostname: this.options.hostname,
135
- key: this.options.https?.key,
136
- onListen: localAddr => {
137
- this.listenAddress = localAddr;
138
- listenReady?.resolve();
139
- this.options.onListen?.(localAddr);
140
- },
141
- port: this.options.port,
142
- signal: abortController.signal
143
- }, async request => {
144
- return await this.handle(request);
145
- });
146
- await listenReady?.promise;
141
+ try {
142
+ const serve = resolveServe(this.options.serve);
143
+ this.dispatcher = dispatcher;
144
+ this.abortController = abortController;
145
+ this.server = serve({
146
+ cert: this.options.https?.cert,
147
+ hostname: this.options.hostname,
148
+ key: this.options.https?.key,
149
+ onListen: localAddr => {
150
+ this.listenAddress = localAddr;
151
+ listenReady?.resolve();
152
+ this.options.onListen?.(localAddr);
153
+ },
154
+ port: this.options.port,
155
+ signal: abortController.signal
156
+ }, async request => {
157
+ return await this.handle(request);
158
+ });
159
+ await listenReady?.promise;
160
+ } catch (error) {
161
+ abortController.abort();
162
+ this.abortController = previousAbortController;
163
+ this.dispatcher = previousDispatcher;
164
+ this.listenAddress = previousListenAddress;
165
+ throw error;
166
+ }
147
167
  }
148
168
  async close() {
149
169
  if (this.closeInFlight) {
@@ -343,15 +363,58 @@ function isWebSocketUpgradeRequest(request) {
343
363
  }
344
364
  function closeDenoServerWithDrain(server, abortController, waitForDrain) {
345
365
  return (async () => {
366
+ let closeFailure;
367
+ const shutdown = Promise.resolve(server.shutdown());
368
+ const drain = waitForDrain();
369
+ const gracefulClose = Promise.allSettled([shutdown, drain]).then(results => ({
370
+ kind: 'graceful',
371
+ results
372
+ }));
373
+ const forcedClose = abortController ? waitForAbort(abortController.signal).then(() => ({
374
+ kind: 'forced'
375
+ })) : undefined;
376
+ const closeResult = forcedClose ? await Promise.race([gracefulClose, forcedClose]) : await gracefulClose;
377
+ if (closeResult.kind === 'graceful') {
378
+ const rejected = closeResult.results.find(result => result.status === 'rejected');
379
+ if (rejected?.status === 'rejected') {
380
+ abortController?.abort();
381
+ closeFailure = {
382
+ error: rejected.reason
383
+ };
384
+ }
385
+ } else {
386
+ try {
387
+ await drain;
388
+ } catch (error) {
389
+ closeFailure = {
390
+ error
391
+ };
392
+ }
393
+ }
346
394
  try {
347
- await server.shutdown();
348
- await waitForDrain();
349
- } finally {
350
- abortController?.abort();
395
+ await server.finished;
396
+ } catch (error) {
397
+ if (!closeFailure) {
398
+ throw error;
399
+ }
400
+ }
401
+ if (closeFailure) {
402
+ throw closeFailure.error;
351
403
  }
352
- await server.finished;
353
404
  })();
354
405
  }
406
+ function waitForAbort(signal) {
407
+ if (signal.aborted) {
408
+ return Promise.resolve();
409
+ }
410
+ return new Promise(resolve => {
411
+ signal.addEventListener('abort', () => {
412
+ resolve();
413
+ }, {
414
+ once: true
415
+ });
416
+ });
417
+ }
355
418
  function createDeferred() {
356
419
  let resolve;
357
420
  let reject;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "platform",
9
9
  "server"
10
10
  ],
11
- "version": "1.1.0",
11
+ "version": "2.0.0",
12
12
  "private": false,
13
13
  "license": "MIT",
14
14
  "repository": {
@@ -32,12 +32,12 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
- "@fluojs/http": "^2.0.1",
36
- "@fluojs/runtime": "^2.0.1"
35
+ "@fluojs/http": "^3.0.0",
36
+ "@fluojs/runtime": "^3.0.0"
37
37
  },
38
38
  "devDependencies": {
39
- "vitest": "^3.2.4",
40
- "@fluojs/testing": "^2.0.0"
39
+ "vitest": "^4.1.11",
40
+ "@fluojs/testing": "^3.0.0"
41
41
  },
42
42
  "scripts": {
43
43
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",