@gjsify/http2 0.4.34 → 0.4.36

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.
@@ -0,0 +1 @@
1
+ import"../../_virtual/_rolldown/runtime.js";const e={setHeader(e,t){return this._headers.set(e.toLowerCase(),typeof t==`number`?String(t):t),this},getHeader(e){return this._headers.get(e.toLowerCase())},removeHeader(e){this._headers.delete(e.toLowerCase())},hasHeader(e){return this._headers.has(e.toLowerCase())},getHeaderNames(){return Array.from(this._headers.keys())},getHeaders(){let e={};for(let[t,n]of this._headers)e[t]=n;return e},appendHeader(e,t){let n=e.toLowerCase(),r=this._headers.get(n);return r===void 0?this._headers.set(n,t):Array.isArray(r)?Array.isArray(t)?r.push(...t):r.push(t):this._headers.set(n,Array.isArray(t)?[r,...t]:[r,t]),this},flushHeaders(){this.headersSent||=!0},writeHead(e,t,n){if(this.statusCode=e,typeof t==`object`&&(n=t,t=void 0),typeof t==`string`&&(this.statusMessage=t),n)for(let[e,t]of Object.entries(n))this.setHeader(e,t);return this},respond(e,t){let n=Number(e[`:status`]??200),r={};for(let[t,n]of Object.entries(e))t!==`:status`&&(r[t]=typeof n==`number`?String(n):n);this.writeHead(n,r),t?.endStream&&this.end()},writeContinue(e){e&&Promise.resolve().then(e)},writeEarlyHints(e,t){t&&Promise.resolve().then(t)},addTrailers(e){},setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}};function installHeaderMethods(t){Object.assign(t,e)}export{installHeaderMethods};
@@ -0,0 +1 @@
1
+ import"../../_virtual/_rolldown/runtime.js";import{Http2ServerResponse as e,ServerHttp2Stream as t}from"../response.js";const n={pushStream(e,t,n){if(typeof t==`function`&&(n=t,t={}),!n)throw TypeError(`callback must be a function`);if(!this._stream){n(Error(`No associated stream`),null,{});return}this._stream.pushStream(e,t,n)},createPushResponse(e,t){if(typeof t!=`function`)throw TypeError(`callback must be a function`);this.pushStream(e,{},(e,n)=>{if(e){t(e,null);return}let r=n._res;t(null,r??null)})}},r={pushStream(n,r,i){if(typeof r==`function`&&(i=r,r={}),!i)throw TypeError(`callback must be a function`);if(this._isPushedStream){let e=Object.assign(Error(`Cannot initiate nested push streams`),{code:`ERR_HTTP2_NESTED_PUSH`});i(e,null,{});return}if(this._session&&this._session.canPush===!1){let e=Object.assign(Error(`HTTP/2 server push has been disabled`),{code:`ERR_HTTP2_PUSH_DISABLED`});i(e,null,{});return}let a,o=null,s={},c={};for(let[e,t]of Object.entries(n))c[e]=typeof t==`number`?String(t):t;c[`:method`]||=`GET`,s=c;let l=this._res.nativeBackend,u=null;if(l){if(u=l.pushPromise(c),!u){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});i(e,null,{});return}a=u.streamId,o=null}else if(this._session){if(a=this._session._allocatePushId(),a===0){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});i(e,null,{});return}o=this._session._buildPushPromise(this.id,a,c)}else a=2;let d=u?new e(null,u):new e(_makeDetachedSoupMessage()),f=new t(d,this._session,{isPushedStream:!0,streamId:a});f._pushPromiseFrame=o,f._pushRequestHeaders=c,d._setStream(f),this._pushedChildren.push(f),Promise.resolve().then(()=>{i(null,f,s)})}};function _makeDetachedSoupMessage(){return null}function installPushMethods(e,t){Object.assign(e,n),Object.assign(t,r)}export{installPushMethods};
@@ -0,0 +1 @@
1
+ import"../../_virtual/_rolldown/runtime.js";import{Buffer as e}from"node:buffer";import{closeSync as t,openSync as n,read as r,statSync as i}from"node:fs";const a={respondWithFD(e,t,n){_respondFromFD(this,e,t,n??{},!1)},respondWithFile(e,t,r){let i;try{i=n(e,`r`)}catch(e){if(r?.onError){r.onError(e);return}throw e}_respondFromFD(this,i,t,r??{},!0)}};function _respondFromFD(n,a,o,s,c){let l=typeof a==`number`?a:a.fd,u=l,d={...o};if(s.statCheck)try{let e=i(_fdPath(l)??`/proc/self/fd/`+l);if(s.statCheck(e,d,s)===!1){c&&t(l),n.end();return}}catch(e){if(s.onError){s.onError(e),c&&t(l);return}}let f=Number(d[`:status`]??200);delete d[`:status`];let p={};for(let[e,t]of Object.entries(d))p[e]=typeof t==`number`?String(t):t;n.writeHead(f,p),n.flushHeaders();let m=Math.max(0,s.offset??0),h=s.length,g=64*1024,_=e.alloc(g),v=m,y=typeof h==`number`?h:1/0,b=0,readNext=()=>{if(y<=0){finish();return}r(u,_,0,Math.min(g,y),v,(t,r)=>{if(t){cleanup(t);return}if(r===0){finish();return}v+=r,b+=r,y-=r;let i=e.allocUnsafe(r);_.copy(i,0,0,r),n.write(i)?readNext():n.once(`drain`,readNext)})},finish=()=>{if(n.end(),c)try{t(u)}catch{}},cleanup=e=>{if(s.onError?s.onError(e):n.destroy(e),c)try{t(u)}catch{}};if(y===0){finish();return}readNext()}function _fdPath(e){return typeof e!=`number`||e<0?null:`/proc/self/fd/`+e}function installRespondWithFileMethods(e){Object.assign(e,a)}export{installRespondWithFileMethods};
@@ -0,0 +1 @@
1
+ import"../../_virtual/_rolldown/runtime.js";import e from"@girs/soup-3.0";import{Writable as t}from"node:stream";import{Buffer as n}from"node:buffer";const r={_startStreaming(){if(this._streaming)return;if(this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!1);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let t=this._soupMsg.get_response_headers();this._headers.has(`content-length`)?t.set_encoding(e.Encoding.CONTENT_LENGTH):t.set_encoding(e.Encoding.CHUNKED);for(let[e,n]of this._headers)if(Array.isArray(n))for(let r of n)t.append(e,r);else t.replace(e,n)},_write(e,t,r){let i=n.isBuffer(e)?e:typeof e==`string`?n.from(e,t):n.from(e);this._startStreaming(),this._nativeBackend?this._nativeBackend.submitData(i,!1):this._soupMsg?(this._soupMsg.get_response_body().append(new Uint8Array(i.buffer,i.byteOffset,i.byteLength)),this._soupMsg.unpause()):this._detachedBody&&this._detachedBody.push(i),r()},_final(e){this._streaming?this._nativeBackend?this._nativeBackend.submitData(n.alloc(0),!0):this._soupMsg&&(this._soupMsg.get_response_body().complete(),this._soupMsg.unpause()):this._sendBatchResponse(),this.finished=!0,e()},_sendBatchResponse(){if(this.headersSent)return;if(this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!0);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let t=this._soupMsg.get_response_headers();for(let[e,n]of this._headers)if(Array.isArray(n))for(let r of n)t.append(e,r);else t.replace(e,n);let n=this._headers.get(`content-type`)||`text/plain`;this._soupMsg.set_response(n,e.MemoryUse.COPY,new Uint8Array)},end(e,n,r){return typeof e==`function`?(r=e,e=void 0):typeof n==`function`&&(r=n,n=void 0),e!=null&&this.write(e,n),t.prototype.end.call(this,r),this}};function installStreamIoMethods(e){Object.assign(e,r)}export{installStreamIoMethods};
@@ -1 +1 @@
1
- import"../_virtual/_rolldown/runtime.js";import{constants as e}from"../protocol.js";import t from"@girs/soup-3.0";import{EventEmitter as n}from"node:events";import{Writable as r}from"node:stream";import{Buffer as i}from"node:buffer";import{closeSync as a,openSync as o,read as s,statSync as c}from"node:fs";var Http2ServerResponse=class extends r{statusCode=200;statusMessage=``;headersSent=!1;finished=!1;sendDate=!0;_soupMsg;_nativeBackend;_headers=new Map;_streaming=!1;_timeoutTimer=null;_stream=null;_detachedBody=null;get stream(){return this._stream}get socket(){return null}get isDetached(){return this._soupMsg===null&&this._nativeBackend===null}get detachedBody(){return this._detachedBody?i.concat(this._detachedBody):null}get isNative(){return this._nativeBackend!==null}get nativeBackend(){return this._nativeBackend}_setStream(e){this._stream=e}_setNativeBackend(e){this._nativeBackend=e}constructor(e,t=null){super(),this._soupMsg=e,this._nativeBackend=t,e===null&&t===null&&(this._detachedBody=[])}setHeader(e,t){return this._headers.set(e.toLowerCase(),typeof t==`number`?String(t):t),this}getHeader(e){return this._headers.get(e.toLowerCase())}removeHeader(e){this._headers.delete(e.toLowerCase())}hasHeader(e){return this._headers.has(e.toLowerCase())}getHeaderNames(){return Array.from(this._headers.keys())}getHeaders(){let e={};for(let[t,n]of this._headers)e[t]=n;return e}appendHeader(e,t){let n=e.toLowerCase(),r=this._headers.get(n);return r===void 0?this._headers.set(n,t):Array.isArray(r)?Array.isArray(t)?r.push(...t):r.push(t):this._headers.set(n,Array.isArray(t)?[r,...t]:[r,t]),this}flushHeaders(){this.headersSent||=!0}writeHead(e,t,n){if(this.statusCode=e,typeof t==`object`&&(n=t,t=void 0),typeof t==`string`&&(this.statusMessage=t),n)for(let[e,t]of Object.entries(n))this.setHeader(e,t);return this}respond(e,t){let n=Number(e[`:status`]??200),r={};for(let[t,n]of Object.entries(e))t!==`:status`&&(r[t]=typeof n==`number`?String(n):n);this.writeHead(n,r),t?.endStream&&this.end()}writeContinue(e){e&&Promise.resolve().then(e)}writeEarlyHints(e,t){t&&Promise.resolve().then(t)}addTrailers(e){}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}_startStreaming(){if(this._streaming)return;if(this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!1);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();this._headers.has(`content-length`)?e.set_encoding(t.Encoding.CONTENT_LENGTH):e.set_encoding(t.Encoding.CHUNKED);for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n)}_write(e,t,n){let r=i.isBuffer(e)?e:typeof e==`string`?i.from(e,t):i.from(e);this._startStreaming(),this._nativeBackend?this._nativeBackend.submitData(r,!1):this._soupMsg?(this._soupMsg.get_response_body().append(new Uint8Array(r.buffer,r.byteOffset,r.byteLength)),this._soupMsg.unpause()):this._detachedBody&&this._detachedBody.push(r),n()}_final(e){this._streaming?this._nativeBackend?this._nativeBackend.submitData(i.alloc(0),!0):this._soupMsg&&(this._soupMsg.get_response_body().complete(),this._soupMsg.unpause()):this._sendBatchResponse(),this.finished=!0,e()}_sendBatchResponse(){if(this.headersSent)return;if(this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!0);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n);let n=this._headers.get(`content-type`)||`text/plain`;this._soupMsg.set_response(n,t.MemoryUse.COPY,new Uint8Array)}end(e,t,n){return typeof e==`function`?(n=e,e=void 0):typeof t==`function`&&(n=t,t=void 0),e!=null&&this.write(e,t),super.end(n),this}respondWithFD(e,t,n){_respondFromFD(this,e,t,n??{},!1)}respondWithFile(e,t,n){let r;try{r=o(e,`r`)}catch(e){if(n?.onError){n.onError(e);return}throw e}_respondFromFD(this,r,t,n??{},!0)}pushStream(e,t,n){if(typeof t==`function`&&(n=t,t={}),!n)throw TypeError(`callback must be a function`);if(!this._stream){n(Error(`No associated stream`),null,{});return}this._stream.pushStream(e,t,n)}createPushResponse(e,t){if(typeof t!=`function`)throw TypeError(`callback must be a function`);this.pushStream(e,{},(e,n)=>{if(e){t(e,null);return}let r=n._res;t(null,r??null)})}},l=class ServerHttp2Stream extends n{id;pushAllowed;sentHeaders={};_res;_session;_isPushedStream;_pushedChildren=[];_pushPromiseFrame=null;_pushRequestHeaders=null;get session(){return this._session}get headersSent(){return this._res.headersSent}get closed(){return this._res.writableEnded}get destroyed(){return this._res.destroyed}get pending(){return!1}get state(){return this.closed?e.NGHTTP2_STREAM_STATE_CLOSED:e.NGHTTP2_STREAM_STATE_OPEN}get pushPromiseFrame(){return this._pushPromiseFrame}get pushRequestHeaders(){return this._pushRequestHeaders}get pushedChildren(){return this._pushedChildren}constructor(e,t=null,n={}){super(),this._res=e,this._session=t,this._isPushedStream=n.isPushedStream===!0,this.id=n.streamId??1,this.pushAllowed=!this._isPushedStream&&t?.canPush!==!1,e.on(`finish`,()=>this.emit(`close`)),e.on(`error`,e=>this.emit(`error`,e))}respond(e,t){this._res.respond(e,t)}write(e,t,n){return this._res.write(e,t,n)}end(e,t,n){return this._res.end(e,t,n),this}destroy(e){return this._res.destroy(e),this}close(t,n){n&&this.once(`close`,n);let r=this._res.nativeBackend;if(r)try{r.reset(t??e.NGHTTP2_NO_ERROR)}catch{}this._res.end()}priority(e){}setTimeout(e,t){return this._res.setTimeout(e,t),this}sendTrailers(e){}additionalHeaders(e){}respondWithFD(e,t,n){this._res.respondWithFD(e,t,n)}respondWithFile(e,t,n){this._res.respondWithFile(e,t,n)}pushStream(e,t,n){if(typeof t==`function`&&(n=t,t={}),!n)throw TypeError(`callback must be a function`);if(this._isPushedStream){let e=Object.assign(Error(`Cannot initiate nested push streams`),{code:`ERR_HTTP2_NESTED_PUSH`});n(e,null,{});return}if(this._session&&this._session.canPush===!1){let e=Object.assign(Error(`HTTP/2 server push has been disabled`),{code:`ERR_HTTP2_PUSH_DISABLED`});n(e,null,{});return}let r,i=null,a={},o={};for(let[t,n]of Object.entries(e))o[t]=typeof n==`number`?String(n):n;o[`:method`]||=`GET`,a=o;let s=this._res.nativeBackend,c=null;if(s){if(c=s.pushPromise(o),!c){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});n(e,null,{});return}r=c.streamId,i=null}else if(this._session){if(r=this._session._allocatePushId(),r===0){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});n(e,null,{});return}i=this._session._buildPushPromise(this.id,r,o)}else r=2;let l=c?new Http2ServerResponse(null,c):new Http2ServerResponse(_makeDetachedSoupMessage()),u=new ServerHttp2Stream(l,this._session,{isPushedStream:!0,streamId:r});u._pushPromiseFrame=i,u._pushRequestHeaders=o,l._setStream(u),this._pushedChildren.push(u),Promise.resolve().then(()=>{n(null,u,a)})}};function _makeDetachedSoupMessage(){return null}function _respondFromFD(e,t,n,r,o){let l=typeof t==`number`?t:t.fd,u=l,d={...n};if(r.statCheck)try{let t=c(_fdPath(l)??`/proc/self/fd/`+l);if(r.statCheck(t,d,r)===!1){o&&a(l),e.end();return}}catch(e){if(r.onError){r.onError(e),o&&a(l);return}}let f=Number(d[`:status`]??200);delete d[`:status`];let p={};for(let[e,t]of Object.entries(d))p[e]=typeof t==`number`?String(t):t;e.writeHead(f,p),e.flushHeaders();let m=Math.max(0,r.offset??0),h=r.length,g=64*1024,_=i.alloc(g),v=m,y=typeof h==`number`?h:1/0,b=0,readNext=()=>{if(y<=0){finish();return}s(u,_,0,Math.min(g,y),v,(t,n)=>{if(t){cleanup(t);return}if(n===0){finish();return}v+=n,b+=n,y-=n;let r=i.allocUnsafe(n);_.copy(r,0,0,n),e.write(r)?readNext():e.once(`drain`,readNext)})},finish=()=>{if(e.end(),o)try{a(u)}catch{}},cleanup=t=>{if(r.onError?r.onError(t):e.destroy(t),o)try{a(u)}catch{}};if(y===0){finish();return}readNext()}function _fdPath(e){return typeof e!=`number`||e<0?null:`/proc/self/fd/`+e}export{Http2ServerResponse,l as ServerHttp2Stream};
1
+ import"../_virtual/_rolldown/runtime.js";import{constants as e}from"../protocol.js";import{installHeaderMethods as t}from"./response/headers.js";import{installStreamIoMethods as n}from"./response/stream-io.js";import{installRespondWithFileMethods as r}from"./response/respond-with-file.js";import{installPushMethods as i}from"./response/push.js";import{EventEmitter as a}from"node:events";import{Writable as o}from"node:stream";import{Buffer as s}from"node:buffer";var Http2ServerResponse=class extends o{statusCode=200;statusMessage=``;headersSent=!1;finished=!1;sendDate=!0;_soupMsg;_nativeBackend;_headers=new Map;_streaming=!1;_timeoutTimer=null;_stream=null;_detachedBody=null;get stream(){return this._stream}get socket(){return null}get isDetached(){return this._soupMsg===null&&this._nativeBackend===null}get detachedBody(){return this._detachedBody?s.concat(this._detachedBody):null}get isNative(){return this._nativeBackend!==null}get nativeBackend(){return this._nativeBackend}_setStream(e){this._stream=e}_setNativeBackend(e){this._nativeBackend=e}constructor(e,t=null){super(),this._soupMsg=e,this._nativeBackend=t,e===null&&t===null&&(this._detachedBody=[])}},ServerHttp2Stream=class extends a{id;pushAllowed;sentHeaders={};_res;_session;_isPushedStream;_pushedChildren=[];_pushPromiseFrame=null;_pushRequestHeaders=null;get session(){return this._session}get headersSent(){return this._res.headersSent}get closed(){return this._res.writableEnded}get destroyed(){return this._res.destroyed}get pending(){return!1}get state(){return this.closed?e.NGHTTP2_STREAM_STATE_CLOSED:e.NGHTTP2_STREAM_STATE_OPEN}get pushPromiseFrame(){return this._pushPromiseFrame}get pushRequestHeaders(){return this._pushRequestHeaders}get pushedChildren(){return this._pushedChildren}constructor(e,t=null,n={}){super(),this._res=e,this._session=t,this._isPushedStream=n.isPushedStream===!0,this.id=n.streamId??1,this.pushAllowed=!this._isPushedStream&&t?.canPush!==!1,e.on(`finish`,()=>this.emit(`close`)),e.on(`error`,e=>this.emit(`error`,e))}respond(e,t){this._res.respond(e,t)}write(e,t,n){return this._res.write(e,t,n)}end(e,t,n){return this._res.end(e,t,n),this}destroy(e){return this._res.destroy(e),this}close(t,n){n&&this.once(`close`,n);let r=this._res.nativeBackend;if(r)try{r.reset(t??e.NGHTTP2_NO_ERROR)}catch{}this._res.end()}priority(e){}setTimeout(e,t){return this._res.setTimeout(e,t),this}sendTrailers(e){}additionalHeaders(e){}respondWithFD(e,t,n){this._res.respondWithFD(e,t,n)}respondWithFile(e,t,n){this._res.respondWithFile(e,t,n)}};t(Http2ServerResponse.prototype),n(Http2ServerResponse.prototype),r(Http2ServerResponse.prototype),i(Http2ServerResponse.prototype,ServerHttp2Stream.prototype);export{Http2ServerResponse,ServerHttp2Stream};
@@ -0,0 +1,25 @@
1
+ import type { Http2ServerResponse } from '../response.js';
2
+ export interface HeaderMethods {
3
+ setHeader(name: string, value: string | number | string[]): Http2ServerResponse;
4
+ getHeader(name: string): string | string[] | undefined;
5
+ removeHeader(name: string): void;
6
+ hasHeader(name: string): boolean;
7
+ getHeaderNames(): string[];
8
+ getHeaders(): Record<string, string | string[]>;
9
+ appendHeader(name: string, value: string | string[]): Http2ServerResponse;
10
+ flushHeaders(): void;
11
+ writeHead(statusCode: number, statusMessage?: string | Record<string, string | string[]>, headers?: Record<string, string | string[]>): Http2ServerResponse;
12
+ respond(headers: Record<string, string | string[] | number>, options?: {
13
+ endStream?: boolean;
14
+ }): void;
15
+ writeContinue(callback?: () => void): void;
16
+ writeEarlyHints(hints: Record<string, string | string[]>, callback?: () => void): void;
17
+ addTrailers(headers: Record<string, string>): void;
18
+ setTimeout(msecs: number, callback?: () => void): Http2ServerResponse;
19
+ }
20
+ declare module '../response.js' {
21
+ interface Http2ServerResponse extends HeaderMethods {
22
+ }
23
+ }
24
+ /** Install header-management methods on Http2ServerResponse.prototype. */
25
+ export declare function installHeaderMethods(proto: object): void;
@@ -0,0 +1,24 @@
1
+ import { Http2ServerResponse, ServerHttp2Stream } from '../response.js';
2
+ export interface ResponsePushMethods {
3
+ pushStream(headers: Record<string, string | string[] | number>, options: {
4
+ parent?: number;
5
+ weight?: number;
6
+ exclusive?: boolean;
7
+ } | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
8
+ createPushResponse(headers: Record<string, string | string[] | number>, callback: (err: Error | null, res: Http2ServerResponse) => void): void;
9
+ }
10
+ export interface StreamPushMethods {
11
+ pushStream(headers: Record<string, string | string[] | number>, options: {
12
+ parent?: number;
13
+ weight?: number;
14
+ exclusive?: boolean;
15
+ } | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
16
+ }
17
+ declare module '../response.js' {
18
+ interface Http2ServerResponse extends ResponsePushMethods {
19
+ }
20
+ interface ServerHttp2Stream extends StreamPushMethods {
21
+ }
22
+ }
23
+ /** Install push-stream methods on both class prototypes. */
24
+ export declare function installPushMethods(responseProto: object, streamProto: object): void;
@@ -0,0 +1,35 @@
1
+ import { type Stats } from 'node:fs';
2
+ import type { OutgoingHttpHeaders } from 'node:http';
3
+ /**
4
+ * StatCheck callback signature — matches Node's
5
+ * `http2.ServerStreamFileResponseOptions.statCheck`:
6
+ * `(stats: fs.Stats, headers: OutgoingHttpHeaders, statOptions: { offset, length }) => void`.
7
+ * The user mutates headers based on stat results; returning `false` cancels
8
+ * the send (Node behaviour — wired through `_respondFromFD`).
9
+ */
10
+ export type StatCheckOptions = {
11
+ offset?: number;
12
+ length?: number;
13
+ };
14
+ export type StatCheck = (stats: Stats, headers: OutgoingHttpHeaders, statOptions: StatCheckOptions) => void | boolean;
15
+ export interface RespondWithFileMethods {
16
+ respondWithFD(fd: number | {
17
+ fd: number;
18
+ }, headers?: Record<string, string | string[] | number>, options?: {
19
+ offset?: number;
20
+ length?: number;
21
+ statCheck?: StatCheck;
22
+ }): void;
23
+ respondWithFile(path: string, headers?: Record<string, string | string[] | number>, options?: {
24
+ offset?: number;
25
+ length?: number;
26
+ statCheck?: StatCheck;
27
+ onError?: (err: Error) => void;
28
+ }): void;
29
+ }
30
+ declare module '../response.js' {
31
+ interface Http2ServerResponse extends RespondWithFileMethods {
32
+ }
33
+ }
34
+ /** Install respondWithFD + respondWithFile on Http2ServerResponse.prototype. */
35
+ export declare function installRespondWithFileMethods(proto: object): void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Internal streaming helpers (no Writable counterpart). Declaration-merged
3
+ * onto `Http2ServerResponse` so sibling methods can call them as `this.*`
4
+ * without type-checker complaints.
5
+ *
6
+ * We deliberately exclude the Writable lifecycle hooks (`_write` / `_final`)
7
+ * and the public `end()` override from the merge — their signatures already
8
+ * exist on the base class via `extends Writable`, and re-declaring them
9
+ * would collide with the inherited `Writable._write(chunk: any, ...)`
10
+ * overload. Those still get attached to the runtime prototype below;
11
+ * `Object.assign` doesn't care about the type-level merge.
12
+ */
13
+ export interface StreamIoMethods {
14
+ _startStreaming(): void;
15
+ _sendBatchResponse(): void;
16
+ }
17
+ declare module '../response.js' {
18
+ interface Http2ServerResponse extends StreamIoMethods {
19
+ }
20
+ }
21
+ /** Install write + chunked-output + end-semantics methods on Http2ServerResponse.prototype. */
22
+ export declare function installStreamIoMethods(proto: object): void;
@@ -1,22 +1,9 @@
1
- import Soup from '@girs/soup-3.0';
1
+ import type Soup from '@girs/soup-3.0';
2
2
  import { EventEmitter } from 'node:events';
3
3
  import { Writable } from 'node:stream';
4
4
  import { Buffer } from 'node:buffer';
5
- import { type Stats } from 'node:fs';
6
- import type { OutgoingHttpHeaders } from 'node:http';
7
5
  import type { ServerHttp2Session } from './session.js';
8
- /**
9
- * StatCheck callback signature — matches Node's
10
- * `http2.ServerStreamFileResponseOptions.statCheck`:
11
- * `(stats: fs.Stats, headers: OutgoingHttpHeaders, statOptions: { offset, length }) => void`.
12
- * The user mutates headers based on stat results; returning `false` cancels
13
- * the send (Node behaviour — wired through `_respondFromFD`).
14
- */
15
- type StatCheckOptions = {
16
- offset?: number;
17
- length?: number;
18
- };
19
- type StatCheck = (stats: Stats, headers: OutgoingHttpHeaders, statOptions: StatCheckOptions) => void | boolean;
6
+ import type { StatCheck } from './response/respond-with-file.js';
20
7
  /**
21
8
  * Per-stream backend that routes writes through `SessionBridge.submit_*`
22
9
  * instead of into a Soup message. Set on responses produced by the native
@@ -41,14 +28,20 @@ export declare class Http2ServerResponse extends Writable {
41
28
  headersSent: boolean;
42
29
  finished: boolean;
43
30
  sendDate: boolean;
44
- private _soupMsg;
45
- private _nativeBackend;
46
- private _headers;
47
- private _streaming;
48
- private _timeoutTimer;
49
- private _stream;
50
- /** Detached responses (PUSH_PROMISE children) buffer their output. */
51
- private _detachedBody;
31
+ /** @internal — sibling response/*.ts modules access this directly. */
32
+ _soupMsg: Soup.ServerMessage | null;
33
+ /** @internal */
34
+ _nativeBackend: Http2NativeBackend | null;
35
+ /** @internal */
36
+ _headers: Map<string, string | string[]>;
37
+ /** @internal */
38
+ _streaming: boolean;
39
+ /** @internal */
40
+ _timeoutTimer: ReturnType<typeof setTimeout> | null;
41
+ /** @internal */
42
+ _stream: ServerHttp2Stream | null;
43
+ /** @internal Detached responses (PUSH_PROMISE children) buffer their output. */
44
+ _detachedBody: Buffer[] | null;
52
45
  get stream(): ServerHttp2Stream | null;
53
46
  get socket(): null;
54
47
  /** Whether this response is detached from a Soup connection (push streams). */
@@ -63,95 +56,23 @@ export declare class Http2ServerResponse extends Writable {
63
56
  /** @internal Used by the native dispatcher to attach its submit backend. */
64
57
  _setNativeBackend(backend: Http2NativeBackend): void;
65
58
  constructor(soupMsg: Soup.ServerMessage | null, nativeBackend?: Http2NativeBackend | null);
66
- setHeader(name: string, value: string | number | string[]): this;
67
- getHeader(name: string): string | string[] | undefined;
68
- removeHeader(name: string): void;
69
- hasHeader(name: string): boolean;
70
- getHeaderNames(): string[];
71
- getHeaders(): Record<string, string | string[]>;
72
- appendHeader(name: string, value: string | string[]): this;
73
- flushHeaders(): void;
74
- writeHead(statusCode: number, statusMessage?: string | Record<string, string | string[]>, headers?: Record<string, string | string[]>): this;
75
- respond(headers: Record<string, string | string[] | number>, options?: {
76
- endStream?: boolean;
77
- }): void;
78
- writeContinue(callback?: () => void): void;
79
- writeEarlyHints(_hints: Record<string, string | string[]>, callback?: () => void): void;
80
- addTrailers(_headers: Record<string, string>): void;
81
- setTimeout(msecs: number, callback?: () => void): this;
82
- private _startStreaming;
83
- _write(chunk: string | Buffer | Uint8Array, encoding: string, callback: (error?: Error | null) => void): void;
84
- _final(callback: (error?: Error | null) => void): void;
85
- private _sendBatchResponse;
86
- end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void): this;
87
- /**
88
- * respondWithFD — stream the contents of an open file descriptor as the
89
- * response body. Headers are sent once `statCheck()` (if provided) has
90
- * had a chance to mutate them; payload is read in 64 KiB chunks via
91
- * `fs.read()` and dispatched through the existing Soup chunked-write path.
92
- *
93
- * Reference: Node.js doc/api/http2.md § respondWithFD()
94
- */
95
- respondWithFD(fd: number | {
96
- fd: number;
97
- }, headers?: Record<string, string | string[] | number>, options?: {
98
- offset?: number;
99
- length?: number;
100
- statCheck?: StatCheck;
101
- }): void;
102
- /**
103
- * respondWithFile — stream a regular file by path. Opens the file with
104
- * fs.openSync, runs the optional `statCheck()` callback so the user can
105
- * mutate headers based on stat results (last-modified, size, etag, …),
106
- * then delegates to the same FD-streaming path as `respondWithFD()`.
107
- *
108
- * Reference: Node.js doc/api/http2.md § respondWithFile()
109
- */
110
- respondWithFile(path: string, headers?: Record<string, string | string[] | number>, options?: {
111
- offset?: number;
112
- length?: number;
113
- statCheck?: StatCheck;
114
- onError?: (err: Error) => void;
115
- }): void;
116
- /**
117
- * pushStream — request the server to push an additional resource on a
118
- * fresh server-initiated stream. The Vala/nghttp2 bridge allocates the
119
- * promised even stream-id and constructs the PUSH_PROMISE frame; wire-level
120
- * delivery requires raw nghttp2-on-socket access that Soup does not expose,
121
- * so the byte-frame is currently a no-op on the wire — but the bridge
122
- * allocator and frame builder are exercised end-to-end and the callback
123
- * receives a fully-usable `ServerHttp2Stream` whose `respond()` / `end()`
124
- * calls write into a synthetic in-memory stream observable from tests.
125
- *
126
- * See STATUS.md "Open TODOs" → "http2 PUSH_PROMISE wire delivery".
127
- */
128
- pushStream(headers: Record<string, string | string[] | number>, options: {
129
- parent?: number;
130
- weight?: number;
131
- exclusive?: boolean;
132
- } | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
133
- /**
134
- * createPushResponse — alternate API: create a child Http2ServerResponse
135
- * for the push without needing to bridge through ServerHttp2Stream. The
136
- * created response shares the parent's stream allocator + bridge.
137
- *
138
- * Reference: Node.js doc/api/http2.md § Http2ServerResponse#createPushResponse()
139
- */
140
- createPushResponse(headers: Record<string, string | string[] | number>, callback: (err: Error | null, res: Http2ServerResponse) => void): void;
141
59
  }
142
60
  export declare class ServerHttp2Stream extends EventEmitter {
143
61
  readonly id: number;
144
62
  readonly pushAllowed: boolean;
145
63
  readonly sentHeaders: Record<string, string | string[]>;
146
- private _res;
147
- private _session;
148
- private _isPushedStream;
149
- /** Children pushed off this request stream (parent → array). */
150
- private _pushedChildren;
151
- /** Cached PUSH_PROMISE frame bytes for inspection in tests. */
152
- private _pushPromiseFrame;
153
- /** Push request headers (`:method`, `:path`, …). */
154
- private _pushRequestHeaders;
64
+ /** @internal — sibling response/*.ts modules access this directly. */
65
+ _res: Http2ServerResponse;
66
+ /** @internal */
67
+ _session: ServerHttp2Session | null;
68
+ /** @internal */
69
+ _isPushedStream: boolean;
70
+ /** @internal Children pushed off this request stream (parent → array). */
71
+ _pushedChildren: ServerHttp2Stream[];
72
+ /** @internal Cached PUSH_PROMISE frame bytes for inspection in tests. */
73
+ _pushPromiseFrame: Uint8Array | null;
74
+ /** @internal Push request headers (`:method`, `:path`, …). */
75
+ _pushRequestHeaders: Record<string, string | string[]> | null;
155
76
  get session(): ServerHttp2Session | null;
156
77
  get headersSent(): boolean;
157
78
  get closed(): boolean;
@@ -199,18 +120,12 @@ export declare class ServerHttp2Stream extends EventEmitter {
199
120
  statCheck?: StatCheck;
200
121
  onError?: (err: Error) => void;
201
122
  }): void;
202
- /**
203
- * pushStream — see {@link Http2ServerResponse.pushStream} for the full
204
- * contract. This is the lower-level entry point: it allocates a promised
205
- * stream-id from the session-bound `GjsifyHttp2.StreamIdAllocator`, builds
206
- * the PUSH_PROMISE frame via `GjsifyHttp2.FrameEncoder`, then synthesises
207
- * a child `ServerHttp2Stream` whose response surface is independent of
208
- * the parent's underlying SoupServerMessage.
209
- */
210
- pushStream(headers: Record<string, string | string[] | number>, options: {
211
- parent?: number;
212
- weight?: number;
213
- exclusive?: boolean;
214
- } | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
215
123
  }
216
- export {};
124
+ import './response/headers.js';
125
+ import './response/headers.js';
126
+ import './response/stream-io.js';
127
+ import './response/stream-io.js';
128
+ import './response/respond-with-file.js';
129
+ import './response/respond-with-file.js';
130
+ import './response/push.js';
131
+ import './response/push.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/http2",
3
- "version": "0.4.34",
3
+ "version": "0.4.36",
4
4
  "description": "Node.js http2 module for Gjs",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -33,19 +33,19 @@
33
33
  "http2"
34
34
  ],
35
35
  "devDependencies": {
36
- "@gjsify/cli": "^0.4.34",
37
- "@gjsify/unit": "^0.4.34",
36
+ "@gjsify/cli": "^0.4.36",
37
+ "@gjsify/unit": "^0.4.36",
38
38
  "@types/node": "^25.9.1",
39
- "typescript": "^6.0.3"
39
+ "typescript": "^5.9.3"
40
40
  },
41
41
  "dependencies": {
42
- "@girs/gio-2.0": "2.88.0-4.0.1",
43
- "@girs/glib-2.0": "2.88.0-4.0.1",
44
- "@girs/gobject-2.0": "2.88.0-4.0.1",
45
- "@girs/soup-3.0": "3.6.6-4.0.1",
46
- "@gjsify/events": "^0.4.34",
47
- "@gjsify/http2-native": "^0.4.34",
48
- "@gjsify/utils": "^0.4.34"
42
+ "@girs/gio-2.0": "2.88.0-4.0.4",
43
+ "@girs/glib-2.0": "2.88.0-4.0.4",
44
+ "@girs/gobject-2.0": "2.88.0-4.0.4",
45
+ "@girs/soup-3.0": "3.6.6-4.0.4",
46
+ "@gjsify/events": "^0.4.36",
47
+ "@gjsify/http2-native": "^0.4.36",
48
+ "@gjsify/utils": "^0.4.36"
49
49
  },
50
50
  "gjsify": {
51
51
  "runtimes": {
@@ -53,5 +53,15 @@
53
53
  "node": "none",
54
54
  "browser": "none"
55
55
  }
56
- }
56
+ },
57
+ "license": "MIT",
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/gjsify/gjsify.git",
61
+ "directory": "packages/node/http2"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/gjsify/gjsify/issues"
65
+ },
66
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/node/http2#readme"
57
67
  }