@gjsify/fetch 0.30.0 → 0.32.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.
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import e from"@girs/soup-3.0";import{validateHeaderName as t,validateHeaderValue as n}from"@gjsify/http/validators";const r=Symbol(`Headers.headers`);function isBoxedPrimitive(e){return e instanceof String||e instanceof Number||e instanceof Boolean||typeof Symbol<`u`&&e instanceof Symbol||typeof BigInt<`u`&&e instanceof BigInt}var i=class Headers{[r];constructor(e){if(this[r]=new Map,e!=null){if(e instanceof Headers){for(let[t,n]of e[r])this[r].set(t,[...n]);return}if(typeof e==`object`&&!isBoxedPrimitive(e)){let r=e[Symbol.iterator];if(r==null)for(let[r,i]of Object.entries(e))t(r),n(r,String(i)),this.append(r,String(i));else{if(typeof r!=`function`)throw TypeError(`Header pairs must be iterable`);for(let r of e){if(typeof r!=`object`||isBoxedPrimitive(r))throw TypeError(`Each header pair must be an iterable object`);let e=[...r];if(e.length!==2)throw TypeError(`Each header pair must be a name/value tuple`);t(e[0]),n(e[0],String(e[1])),this.append(e[0],String(e[1]))}}}else throw TypeError(`Failed to construct 'Headers': The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'`)}}append(e,i){t(e),n(e,i);let a=String(e).toLowerCase(),o=String(i),s=this[r].get(a);s?s.push(o):this[r].set(a,[o])}set(e,i){t(e),n(e,i);let a=String(e).toLowerCase();this[r].set(a,[String(i)])}delete(e){this[r].delete(String(e).toLowerCase())}has(e){return this[r].has(String(e).toLowerCase())}get(e){let t=this[r].get(String(e).toLowerCase());if(!t||t.length===0)return null;let n=t.join(`, `);return/^content-encoding$/i.test(e)&&(n=n.toLowerCase()),n}getAll(e){return this[r].get(String(e).toLowerCase())??[]}getSetCookie(){return this[r].get(`set-cookie`)??[]}forEach(e,t){for(let n of this.keys())Reflect.apply(e,t,[this.get(n),n,this])}*keys(){let e=[...this[r].keys()].sort(),t=new Set;for(let n of e)t.has(n)||(t.add(n),yield n)}*values(){for(let e of this.keys())yield this.get(e)}*entries(){for(let e of this.keys())yield[e,this.get(e)]}[Symbol.iterator](){return this.entries()}get[Symbol.toStringTag](){return`Headers`}toString(){return Object.prototype.toString.call(this)}raw(){let e={};for(let t of this.keys())e[t]=this.getAll(t);return e}_appendToSoupMessage(t,n=e.MessageHeadersType.REQUEST){let r=t?t.get_request_headers():new e.MessageHeaders(n);for(let[e,t]of this.entries())r.append(e,t);return r}static _newFromSoupMessage(t,n=e.MessageHeadersType.RESPONSE){let r=new Headers,i;return i=n===e.MessageHeadersType.RESPONSE?t.get_response_headers():t.get_request_headers(),i.foreach((e,t)=>{r.append(e,t)}),r}[Symbol.for(`nodejs.util.inspect.custom`)](){let e={};for(let t of this.keys()){let n=this.getAll(t);t===`host`?e[t]=n[0]:e[t]=n.length>1?n:n[0]}return e}};Object.defineProperties(i.prototype,[`get`,`entries`,`forEach`,`values`].reduce((e,t)=>(e[t]={enumerable:!0},e),{}));export{i as default};
1
+ import"./_virtual/_rolldown/runtime.js";import{validateHeaderName as e,validateHeaderValue as t}from"@gjsify/http/validators";const n=Symbol(`Headers.headers`);function isBoxedPrimitive(e){return e instanceof String||e instanceof Number||e instanceof Boolean||typeof Symbol<`u`&&e instanceof Symbol||typeof BigInt<`u`&&e instanceof BigInt}var r=class Headers{[n];constructor(r){if(this[n]=new Map,r!=null){if(r instanceof Headers){for(let[e,t]of r[n])this[n].set(e,[...t]);return}if(typeof r==`object`&&!isBoxedPrimitive(r)){let n=r[Symbol.iterator];if(n==null)for(let[n,i]of Object.entries(r))e(n),t(n,String(i)),this.append(n,String(i));else{if(typeof n!=`function`)throw TypeError(`Header pairs must be iterable`);for(let n of r){if(typeof n!=`object`||isBoxedPrimitive(n))throw TypeError(`Each header pair must be an iterable object`);let r=[...n];if(r.length!==2)throw TypeError(`Each header pair must be a name/value tuple`);e(r[0]),t(r[0],String(r[1])),this.append(r[0],String(r[1]))}}}else throw TypeError(`Failed to construct 'Headers': The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'`)}}append(r,i){e(r),t(r,i);let a=String(r).toLowerCase(),o=String(i),s=this[n].get(a);s?s.push(o):this[n].set(a,[o])}set(r,i){e(r),t(r,i);let a=String(r).toLowerCase();this[n].set(a,[String(i)])}delete(e){this[n].delete(String(e).toLowerCase())}has(e){return this[n].has(String(e).toLowerCase())}get(e){let t=this[n].get(String(e).toLowerCase());if(!t||t.length===0)return null;let r=t.join(`, `);return/^content-encoding$/i.test(e)&&(r=r.toLowerCase()),r}getAll(e){return this[n].get(String(e).toLowerCase())??[]}getSetCookie(){return this[n].get(`set-cookie`)??[]}forEach(e,t){for(let n of this.keys())Reflect.apply(e,t,[this.get(n),n,this])}*keys(){let e=[...this[n].keys()].sort(),t=new Set;for(let n of e)t.has(n)||(t.add(n),yield n)}*values(){for(let e of this.keys())yield this.get(e)}*entries(){for(let e of this.keys())yield[e,this.get(e)]}[Symbol.iterator](){return this.entries()}get[Symbol.toStringTag](){return`Headers`}toString(){return Object.prototype.toString.call(this)}raw(){let e={};for(let t of this.keys())e[t]=this.getAll(t);return e}_appendToSoupMessage(e,t,n=e.MessageHeadersType.REQUEST){let r=t?t.get_request_headers():new e.MessageHeaders(n);for(let[e,t]of this.entries())r.append(e,t);return r}static _newFromSoupMessage(e,t,n=e.MessageHeadersType.RESPONSE){let r=new Headers,i;return i=n===e.MessageHeadersType.RESPONSE?t.get_response_headers():t.get_request_headers(),i.foreach((e,t)=>{r.append(e,t)}),r}[Symbol.for(`nodejs.util.inspect.custom`)](){let e={};for(let t of this.keys()){let n=this.getAll(t);t===`host`?e[t]=n[0]:e[t]=n.length>1?n:n[0]}return e}};Object.defineProperties(r.prototype,[`get`,`entries`,`forEach`,`values`].reduce((e,t)=>(e[t]={enumerable:!0},e),{}));export{r as default};
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{Blob as e,File as t}from"./utils/blob-from.js";import{FetchError as n}from"./errors/fetch-error.js";import{isDomainOrSubdomain as r,isSameProtocol as i}from"./utils/is.js";import{clone as a}from"./body.js";import{AbortError as o}from"./errors/abort-error.js";import s from"./headers.js";import{parseDataUri as c}from"./utils/data-uri.js";import{resolveRootRelativeUrl as l}from"./utils/root-relative-system.js";import{isRedirect as u}from"./utils/is-redirect.js";import{Response as d}from"./response.js";import{parseReferrerPolicyFromHeader as f}from"./utils/referrer.js";import{Request as p,getSoupRequestOptions as m}from"./request.js";import{XMLHttpRequest as h,XMLHttpRequestUpload as g}from"./xhr.js";import{URL as _}from"@gjsify/url";import v from"node:stream";import{FormData as y}from"@gjsify/formdata";import b from"@girs/glib-2.0";const x=new Set([`data:`,`http:`,`https:`,`file:`]);function rewriteFetchInput(e){if(typeof e!=`string`)return e;let t=l(e);return t!==e&&globalThis.__GJSIFY_DEBUG_FETCH===!0&&console.log(`[fetch] rewrite ${e} → ${t}`),t}async function fetch(t,l={}){t=rewriteFetchInput(t);let h=new p(t,l),{parsedURL:g,options:y}=m(h);if(!x.has(g.protocol))throw TypeError(`@gjsify/fetch cannot load ${t}. URL scheme "${g.protocol.replace(/:$/,``)}" is not supported.`);if(g.protocol===`data:`){let{buffer:e,typeFull:t}=c(h.url);return new d(Buffer.from(e),{headers:{"Content-Type":t}})}if(g.protocol===`file:`){let e=globalThis.__GJSIFY_DEBUG_FETCH===!0;e&&console.log(`[fetch] file:// ${h.url}`);try{let t=b.filename_from_uri(h.url)[0];e&&console.log(`[fetch] file:// path=${t}`);let[r,i]=b.file_get_contents(t);if(e&&console.log(`[fetch] file:// ok=${r} bytes=${i?.byteLength??`?`}`),!r)throw new n(`Failed to read file: ${t}`,`system`);let a=i,o=new Uint8Array(a.byteLength);o.set(a);let s=new d(o);return e&&console.log(`[fetch] file:// response created`),s}catch(t){let r=t instanceof Error?t:Error(String(t));throw e&&console.warn(`[fetch] file:// FAIL: ${r.message}`),new n(`request to ${h.url} failed, reason: ${r.message}`,`system`,r)}}let{signal:S}=h;if(S&&S.aborted)throw new o(`The operation was aborted.`);let C,w;try{let e=await h._send(y);C=e.readable,w=e.cancellable}catch(e){if(S&&S.aborted)throw new o(`The operation was aborted.`);let t=e instanceof Error?e:Error(String(e));throw new n(`request to ${h.url} failed, reason: ${t.message}`,`system`,t)}let abortHandler=()=>{w.cancel()};S&&S.addEventListener(`abort`,abortHandler,{once:!0});let finalize=()=>{S&&S.removeEventListener(`abort`,abortHandler)};w.connect(()=>{C.destroy(new o(`The operation was aborted.`))}),C.on(`error`,e=>{finalize()});let T=h._message,E=s._newFromSoupMessage(T),D=T.status_code,O=T.get_reason_phrase();if(u(D)){let e=E.get(`Location`),t=null;try{t=e===null?null:new _(e,h.url)}catch{if(h.redirect!==`manual`)throw finalize(),new n(`uri requested responds with an invalid redirect URL: ${e}`,`invalid-redirect`)}switch(h.redirect){case`error`:throw finalize(),new n(`uri requested responds with a redirect, redirect mode is set to error: ${h.url}`,`no-redirect`);case`manual`:break;case`follow`:{if(t===null)break;if(h.counter>=h.follow)throw finalize(),new n(`maximum redirect reached at: ${h.url}`,`max-redirect`);let e={headers:new s(h.headers),follow:h.follow,counter:h.counter+1,agent:h.agent,compress:h.compress,method:h.method,body:a(h),signal:h.signal,size:h.size,referrer:h.referrer,referrerPolicy:h.referrerPolicy};if(!r(h.url,t)||!i(h.url,t))for(let t of[`authorization`,`www-authenticate`,`cookie`,`cookie2`])e.headers.delete(t);if(D!==303&&h.body&&l.body instanceof v.Readable)throw finalize(),new n(`Cannot follow redirect with body being a readable stream`,`unsupported-redirect`);(D===303||(D===301||D===302)&&h.method===`POST`)&&(e.method=`GET`,e.body=void 0,e.headers.delete(`content-length`));let o=f(E);return o&&(e.referrerPolicy=o),finalize(),fetch(new p(t,e))}default:throw TypeError(`Redirect option '${h.redirect}' is not a valid value of RequestRedirect`)}}let k={url:h.url,status:D,statusText:O,headers:E,size:h.size,counter:h.counter,highWaterMark:h.highWaterMark},A=E.get(`Content-Encoding`);if(!h.compress||h.method===`HEAD`||A===null||D===204||D===304)return finalize(),new d(C,k);if(typeof DecompressionStream<`u`){let t=null;if(A===`gzip`||A===`x-gzip`?t=`gzip`:(A===`deflate`||A===`x-deflate`)&&(t=`deflate`),t){let n=new e([await new d(C,k).arrayBuffer()]).stream().pipeThrough(new DecompressionStream(t));return finalize(),new d(n,k)}}return finalize(),new d(C,k)}export{o as AbortError,e as Blob,n as FetchError,t as File,y as FormData,s as Headers,p as Request,d as Response,h as XMLHttpRequest,g as XMLHttpRequestUpload,fetch as default,u as isRedirect,l as resolveRootRelativeUrl};
1
+ import"./_virtual/_rolldown/runtime.js";import{Blob as e,File as t}from"./utils/blob-from.js";import{FetchError as n}from"./errors/fetch-error.js";import{isDomainOrSubdomain as r,isSameProtocol as i}from"./utils/is.js";import{clone as a}from"./body.js";import{AbortError as o}from"./errors/abort-error.js";import s from"./headers.js";import{parseDataUri as c}from"./utils/data-uri.js";import{resolveRootRelativeUrl as l}from"./utils/root-relative-system.js";import{isRedirect as u}from"./utils/is-redirect.js";import{Response as d}from"./response.js";import{loadSoup as f}from"./utils/soup-lazy.js";import{parseReferrerPolicyFromHeader as p}from"./utils/referrer.js";import{Request as m,getSoupRequestOptions as h}from"./request.js";import{XMLHttpRequest as g,XMLHttpRequestUpload as _}from"./xhr.js";import{URL as v}from"@gjsify/url";import y from"node:stream";import{FormData as b}from"@gjsify/formdata";import x from"@girs/glib-2.0";const S=new Set([`data:`,`http:`,`https:`,`file:`]);function rewriteFetchInput(e){if(typeof e!=`string`)return e;let t=l(e);return t!==e&&globalThis.__GJSIFY_DEBUG_FETCH===!0&&console.log(`[fetch] rewrite ${e} → ${t}`),t}async function fetch(t,l={}){t=rewriteFetchInput(t);let g=new m(t,l),{parsedURL:_,options:b}=h(g);if(!S.has(_.protocol))throw TypeError(`@gjsify/fetch cannot load ${t}. URL scheme "${_.protocol.replace(/:$/,``)}" is not supported.`);if(_.protocol===`data:`){let{buffer:e,typeFull:t}=c(g.url);return new d(Buffer.from(e),{headers:{"Content-Type":t}})}if(_.protocol===`file:`){let e=globalThis.__GJSIFY_DEBUG_FETCH===!0;e&&console.log(`[fetch] file:// ${g.url}`);try{let t=x.filename_from_uri(g.url)[0];e&&console.log(`[fetch] file:// path=${t}`);let[r,i]=x.file_get_contents(t);if(e&&console.log(`[fetch] file:// ok=${r} bytes=${i?.byteLength??`?`}`),!r)throw new n(`Failed to read file: ${t}`,`system`);let a=i,o=new Uint8Array(a.byteLength);o.set(a);let s=new d(o);return e&&console.log(`[fetch] file:// response created`),s}catch(t){let r=t instanceof Error?t:Error(String(t));throw e&&console.warn(`[fetch] file:// FAIL: ${r.message}`),new n(`request to ${g.url} failed, reason: ${r.message}`,`system`,r)}}let{signal:C}=g;if(C&&C.aborted)throw new o(`The operation was aborted.`);let w,T;try{let e=await g._send(b);w=e.readable,T=e.cancellable}catch(e){if(C&&C.aborted)throw new o(`The operation was aborted.`);let t=e instanceof Error?e:Error(String(e));throw new n(`request to ${g.url} failed, reason: ${t.message}`,`system`,t)}let abortHandler=()=>{T.cancel()};C&&C.addEventListener(`abort`,abortHandler,{once:!0});let finalize=()=>{C&&C.removeEventListener(`abort`,abortHandler)};T.connect(()=>{w.destroy(new o(`The operation was aborted.`))}),w.on(`error`,e=>{finalize()});let E=g._message,D=await f(),O=s._newFromSoupMessage(D,E),k=E.status_code,A=E.get_reason_phrase();if(u(k)){let e=O.get(`Location`),t=null;try{t=e===null?null:new v(e,g.url)}catch{if(g.redirect!==`manual`)throw finalize(),new n(`uri requested responds with an invalid redirect URL: ${e}`,`invalid-redirect`)}switch(g.redirect){case`error`:throw finalize(),new n(`uri requested responds with a redirect, redirect mode is set to error: ${g.url}`,`no-redirect`);case`manual`:break;case`follow`:{if(t===null)break;if(g.counter>=g.follow)throw finalize(),new n(`maximum redirect reached at: ${g.url}`,`max-redirect`);let e={headers:new s(g.headers),follow:g.follow,counter:g.counter+1,agent:g.agent,compress:g.compress,method:g.method,body:a(g),signal:g.signal,size:g.size,referrer:g.referrer,referrerPolicy:g.referrerPolicy};if(!r(g.url,t)||!i(g.url,t))for(let t of[`authorization`,`www-authenticate`,`cookie`,`cookie2`])e.headers.delete(t);if(k!==303&&g.body&&l.body instanceof y.Readable)throw finalize(),new n(`Cannot follow redirect with body being a readable stream`,`unsupported-redirect`);(k===303||(k===301||k===302)&&g.method===`POST`)&&(e.method=`GET`,e.body=void 0,e.headers.delete(`content-length`));let o=p(O);return o&&(e.referrerPolicy=o),finalize(),fetch(new m(t,e))}default:throw TypeError(`Redirect option '${g.redirect}' is not a valid value of RequestRedirect`)}}let j={url:g.url,status:k,statusText:A,headers:O,size:g.size,counter:g.counter,highWaterMark:g.highWaterMark},M=O.get(`Content-Encoding`);if(!g.compress||g.method===`HEAD`||M===null||k===204||k===304)return finalize(),new d(w,j);if(typeof DecompressionStream<`u`){let t=null;if(M===`gzip`||M===`x-gzip`?t=`gzip`:(M===`deflate`||M===`x-deflate`)&&(t=`deflate`),t){let n=new e([await new d(w,j).arrayBuffer()]).stream().pipeThrough(new DecompressionStream(t));return finalize(),new d(n,j)}}return finalize(),new d(w,j)}export{o as AbortError,e as Blob,n as FetchError,t as File,b as FormData,s as Headers,m as Request,d as Response,g as XMLHttpRequest,_ as XMLHttpRequestUpload,fetch as default,u as isRedirect,l as resolveRootRelativeUrl};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{isAbortSignal as e}from"./utils/is.js";import t,{clone as n,extractContentType as r,getTotalBytes as i}from"./body.js";import a from"./headers.js";import{inputStreamToReadable as o,soupSendAsync as s}from"./utils/soup-helpers.js";import{DEFAULT_REFERRER_POLICY as c,determineRequestsReferrer as l,validateReferrerPolicy as u}from"./utils/referrer.js";import{URL as d}from"@gjsify/url";import f from"@girs/soup-3.0";import p from"@girs/glib-2.0";import m from"@girs/gio-2.0";const h=Symbol(`Request internals`);let g=null;function envPositiveInt(e,t){let n=p.getenv(e);if(!n)return t;let r=Number.parseInt(n,10);return Number.isFinite(r)&&r>0?r:t}function shouldAcceptAnyCert(e){return e===!1?!0:e===!0?!1:p.getenv(`NODE_TLS_REJECT_UNAUTHORIZED`)===`0`}function getSharedSession(){if(g===null){let e=envPositiveInt(`GJSIFY_FETCH_MAX_CONNS_PER_HOST`,16),t=Math.max(envPositiveInt(`GJSIFY_FETCH_MAX_CONNS`,64),e);g=new f.Session({maxConns:t,maxConnsPerHost:e}),g.remove_feature_by_type(f.ContentDecoder.$gtype)}return g}const isRequest=e=>typeof e==`object`&&typeof e.url==`string`;var _=class Request extends t{cache;credentials;destination;get headers(){return this[h].headers}integrity;keepalive;get method(){return this[h].method}mode;get redirect(){return this[h].redirect}get referrer(){if(this[h].referrer===`no-referrer`)return``;if(this[h].referrer===`client`)return`about:client`;if(this[h].referrer)return this[h].referrer.toString()}get referrerPolicy(){return this[h].referrerPolicy}set referrerPolicy(e){this[h].referrerPolicy=u(e)}get signal(){return this[h].signal}get url(){return this[h].parsedURL.toString()}get _uri(){return p.Uri.parse(this.url,p.UriFlags.ENCODED)}get _session(){return this[h].session}get _message(){return this[h].message}get _inputStream(){return this[h].inputStream}get[Symbol.toStringTag](){return`Request`}[h];follow;compress=!1;counter=0;agent=``;highWaterMark=16384;insecureHTTPParser=!1;rejectUnauthorized;constructor(t,i){let o=t,s=i||{},c,l={};if(isRequest(t)?(c=new d(o.url),l=o):c=new d(t),c.username!==``||c.password!==``)throw TypeError(`${c} is an url with embedded credentials.`);let u=s.method||l.method||`GET`;if(/^(delete|get|head|options|post|put)$/i.test(u)&&(u=u.toUpperCase()),(i?.body!=null||isRequest(t)&&o.body!==null)&&(u===`GET`||u===`HEAD`))throw TypeError(`Request with GET/HEAD method cannot have body`);let m=i?.body?i.body:isRequest(t)&&o.body!==null?n(t):null;super(m,{size:s.size||0});let g=new a(i?.headers||o.headers||{});if(m!==null&&!g.has(`Content-Type`)){let e=r(m,this);e&&g.set(`Content-Type`,e)}let _=isRequest(t)?o.signal:null;if(i&&`signal`in i&&(_=i.signal),_!=null&&!e(_))throw TypeError(`Expected signal to be an instanceof AbortSignal or EventTarget`);let v=i?.referrer==null?o.referrer:i.referrer;if(v===``)v=`no-referrer`;else if(v){let e=new d(v);v=/^about:(\/\/)?client$/.test(e.toString())?`client`:e}else v=void 0;let y=c.protocol,b=null,x=null;(y===`http:`||y===`https:`)&&(b=getSharedSession(),x=new f.Message({method:u,uri:p.Uri.parse(c.toString(),p.UriFlags.ENCODED)})),this[h]={method:u,redirect:i?.redirect||o.redirect||`follow`,headers:g,parsedURL:c,signal:_,referrer:v,referrerPolicy:``,session:b,message:x},this.follow=s.follow===void 0?o.follow===void 0?20:o.follow:s.follow,this.compress=s.compress===void 0?o.compress===void 0?!0:o.compress:s.compress,this.counter=s.counter||o.counter||0,this.agent=s.agent||o.agent,this.highWaterMark=s.highWaterMark||o.highWaterMark||16384,this.insecureHTTPParser=s.insecureHTTPParser||o.insecureHTTPParser||!1,this.rejectUnauthorized=s.rejectUnauthorized??o.rejectUnauthorized,this.referrerPolicy=i?.referrerPolicy||o.referrerPolicy||``}async _send(e){let{session:t,message:n,signal:r,parsedURL:i}=this[h];if(!t||!n)throw Error(`Cannot send request: no Soup session (non-HTTP URL?)`);e.headers._appendToSoupMessage(n);let a=this._rawBodyBuffer;if(a!==null&&a.byteLength>0){let t=e.headers.get(`content-type`)||null;n.set_request_body_from_bytes(t,new p.Bytes(a))}i.protocol===`https:`&&shouldAcceptAnyCert(this.rejectUnauthorized)&&n.connect(`accept-certificate`,(e,t,n)=>!0);let c=new m.Cancellable;return r&&(r.aborted?c.cancel():r.addEventListener(`abort`,()=>c.cancel(),{once:!0})),this[h].inputStream=await s(t,n,p.PRIORITY_DEFAULT,c),this[h].readable=o(this[h].inputStream),{inputStream:this[h].inputStream,readable:this[h].readable,cancellable:c}}clone(){return new Request(this)}async arrayBuffer(){return super.arrayBuffer()}async blob(){return super.blob()}async formData(){return super.formData()}async json(){return super.json()}async text(){return super.text()}};Object.defineProperties(_.prototype,{method:{enumerable:!0},url:{enumerable:!0},headers:{enumerable:!0},redirect:{enumerable:!0},clone:{enumerable:!0},signal:{enumerable:!0},referrer:{enumerable:!0},referrerPolicy:{enumerable:!0}});const getSoupRequestOptions=e=>{let{parsedURL:t}=e[h],n=new a(e[h].headers);n.has(`Accept`)||n.set(`Accept`,`*/*`);let r=null;if(e.body===null&&/^(post|put)$/i.test(e.method)&&(r=`0`),e.body!==null){let t=i(e);typeof t==`number`&&!Number.isNaN(t)&&(r=String(t))}r&&n.set(`Content-Length`,r),e.referrerPolicy===``&&(e.referrerPolicy=c),e.referrer&&e.referrer!==`no-referrer`?e[h].referrer=l(e):e[h].referrer=`no-referrer`,e[h].referrer instanceof d&&n.set(`Referer`,e.referrer),n.has(`User-Agent`)||n.set(`User-Agent`,`gjsify-fetch`),e.compress&&!n.has(`Accept-Encoding`)&&n.set(`Accept-Encoding`,`gzip, deflate`);let{agent:o}=e;return typeof o==`function`&&(o=o(t)),!n.has(`Connection`)&&!o&&n.set(`Connection`,`close`),{parsedURL:t,options:{headers:n}}};export{_ as Request,_ as default,getSoupRequestOptions};
1
+ import"./_virtual/_rolldown/runtime.js";import{isAbortSignal as e}from"./utils/is.js";import t,{clone as n,extractContentType as r,getTotalBytes as i}from"./body.js";import a from"./headers.js";import{inputStreamToReadable as o,soupSendAsync as s}from"./utils/soup-helpers.js";import{loadSoup as c}from"./utils/soup-lazy.js";import{DEFAULT_REFERRER_POLICY as l,determineRequestsReferrer as u,validateReferrerPolicy as d}from"./utils/referrer.js";import{URL as f}from"@gjsify/url";import p from"@girs/glib-2.0";import m from"@girs/gio-2.0";const h=Symbol(`Request internals`);let g=null;function envPositiveInt(e,t){let n=p.getenv(e);if(!n)return t;let r=Number.parseInt(n,10);return Number.isFinite(r)&&r>0?r:t}function shouldAcceptAnyCert(e){return e===!1?!0:e===!0?!1:p.getenv(`NODE_TLS_REJECT_UNAUTHORIZED`)===`0`}function getSharedSession(e){if(g===null){let t=envPositiveInt(`GJSIFY_FETCH_MAX_CONNS_PER_HOST`,16),n=Math.max(envPositiveInt(`GJSIFY_FETCH_MAX_CONNS`,64),t);g=new e.Session({maxConns:n,maxConnsPerHost:t}),g.remove_feature_by_type(e.ContentDecoder.$gtype)}return g}const isRequest=e=>typeof e==`object`&&typeof e.url==`string`;var _=class Request extends t{cache;credentials;destination;get headers(){return this[h].headers}integrity;keepalive;get method(){return this[h].method}mode;get redirect(){return this[h].redirect}get referrer(){if(this[h].referrer===`no-referrer`)return``;if(this[h].referrer===`client`)return`about:client`;if(this[h].referrer)return this[h].referrer.toString()}get referrerPolicy(){return this[h].referrerPolicy}set referrerPolicy(e){this[h].referrerPolicy=d(e)}get signal(){return this[h].signal}get url(){return this[h].parsedURL.toString()}get _uri(){return p.Uri.parse(this.url,p.UriFlags.ENCODED)}get _session(){return this[h].session}get _message(){return this[h].message}get _inputStream(){return this[h].inputStream}get[Symbol.toStringTag](){return`Request`}[h];follow;compress=!1;counter=0;agent=``;highWaterMark=16384;insecureHTTPParser=!1;rejectUnauthorized;constructor(t,i){let o=t,s=i||{},c,l={};if(isRequest(t)?(c=new f(o.url),l=o):c=new f(t),c.username!==``||c.password!==``)throw TypeError(`${c} is an url with embedded credentials.`);let u=s.method||l.method||`GET`;if(/^(delete|get|head|options|post|put)$/i.test(u)&&(u=u.toUpperCase()),(i?.body!=null||isRequest(t)&&o.body!==null)&&(u===`GET`||u===`HEAD`))throw TypeError(`Request with GET/HEAD method cannot have body`);let d=i?.body?i.body:isRequest(t)&&o.body!==null?n(t):null;super(d,{size:s.size||0});let p=new a(i?.headers||o.headers||{});if(d!==null&&!p.has(`Content-Type`)){let e=r(d,this);e&&p.set(`Content-Type`,e)}let m=isRequest(t)?o.signal:null;if(i&&`signal`in i&&(m=i.signal),m!=null&&!e(m))throw TypeError(`Expected signal to be an instanceof AbortSignal or EventTarget`);let g=i?.referrer==null?o.referrer:i.referrer;if(g===``)g=`no-referrer`;else if(g){let e=new f(g);g=/^about:(\/\/)?client$/.test(e.toString())?`client`:e}else g=void 0;this[h]={method:u,redirect:i?.redirect||o.redirect||`follow`,headers:p,parsedURL:c,signal:m,referrer:g,referrerPolicy:``,session:null,message:null},this.follow=s.follow===void 0?o.follow===void 0?20:o.follow:s.follow,this.compress=s.compress===void 0?o.compress===void 0?!0:o.compress:s.compress,this.counter=s.counter||o.counter||0,this.agent=s.agent||o.agent,this.highWaterMark=s.highWaterMark||o.highWaterMark||16384,this.insecureHTTPParser=s.insecureHTTPParser||o.insecureHTTPParser||!1,this.rejectUnauthorized=s.rejectUnauthorized??o.rejectUnauthorized,this.referrerPolicy=i?.referrerPolicy||o.referrerPolicy||``}async _send(e){let{signal:t,parsedURL:n}=this[h],r=n.protocol;if(r!==`http:`&&r!==`https:`)throw Error(`Cannot send request: no Soup session (non-HTTP URL?)`);let i=await c(),a=getSharedSession(i),l=new i.Message({method:this[h].method,uri:p.Uri.parse(n.toString(),p.UriFlags.ENCODED)});this[h].session=a,this[h].message=l,e.headers._appendToSoupMessage(i,l);let u=this._rawBodyBuffer;if(u!==null&&u.byteLength>0){let t=e.headers.get(`content-type`)||null;l.set_request_body_from_bytes(t,new p.Bytes(u))}n.protocol===`https:`&&shouldAcceptAnyCert(this.rejectUnauthorized)&&l.connect(`accept-certificate`,(e,t,n)=>!0);let d=new m.Cancellable;return t&&(t.aborted?d.cancel():t.addEventListener(`abort`,()=>d.cancel(),{once:!0})),this[h].inputStream=await s(a,l,p.PRIORITY_DEFAULT,d),this[h].readable=o(this[h].inputStream),{inputStream:this[h].inputStream,readable:this[h].readable,cancellable:d}}clone(){return new Request(this)}async arrayBuffer(){return super.arrayBuffer()}async blob(){return super.blob()}async formData(){return super.formData()}async json(){return super.json()}async text(){return super.text()}};Object.defineProperties(_.prototype,{method:{enumerable:!0},url:{enumerable:!0},headers:{enumerable:!0},redirect:{enumerable:!0},clone:{enumerable:!0},signal:{enumerable:!0},referrer:{enumerable:!0},referrerPolicy:{enumerable:!0}});const getSoupRequestOptions=e=>{let{parsedURL:t}=e[h],n=new a(e[h].headers);n.has(`Accept`)||n.set(`Accept`,`*/*`);let r=null;if(e.body===null&&/^(post|put)$/i.test(e.method)&&(r=`0`),e.body!==null){let t=i(e);typeof t==`number`&&!Number.isNaN(t)&&(r=String(t))}r&&n.set(`Content-Length`,r),e.referrerPolicy===``&&(e.referrerPolicy=l),e.referrer&&e.referrer!==`no-referrer`?e[h].referrer=u(e):e[h].referrer=`no-referrer`,e[h].referrer instanceof f&&n.set(`Referer`,e.referrer),n.has(`User-Agent`)||n.set(`User-Agent`,`gjsify-fetch`),e.compress&&!n.has(`Accept-Encoding`)&&n.set(`Accept-Encoding`,`gzip, deflate`);let{agent:o}=e;return typeof o==`function`&&(o=o(t)),!n.has(`Connection`)&&!o&&n.set(`Connection`,`close`),{parsedURL:t,options:{headers:n}}};export{_ as Request,_ as default,getSoupRequestOptions};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";let e=null;async function loadSoup(){return e??=(await import(`gi://Soup?version=3.0`)).default}export{loadSoup};
package/lib/headers.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import Soup from '@girs/soup-3.0';
1
+ import type Soup from '@girs/soup-3.0';
2
2
  declare const _headers: unique symbol;
3
3
  export default class Headers implements Iterable<[string, string]> {
4
4
  [_headers]: Map<string, string[]>;
@@ -23,11 +23,15 @@ export default class Headers implements Iterable<[string, string]> {
23
23
  raw(): Record<string, string[]>;
24
24
  /**
25
25
  * Append all headers to a Soup.Message for sending.
26
+ *
27
+ * `soup` is the lazily-loaded Soup namespace (see utils/soup-lazy.ts); it is
28
+ * passed in rather than statically imported so this module does not link the
29
+ * `gi://Soup` typelib at import time.
26
30
  */
27
- _appendToSoupMessage(message?: Soup.Message, type?: Soup.MessageHeadersType): Soup.MessageHeaders;
31
+ _appendToSoupMessage(soup: typeof Soup, message?: Soup.Message, type?: Soup.MessageHeadersType): Soup.MessageHeaders;
28
32
  /**
29
33
  * Create a Headers instance from a Soup.Message's headers.
30
34
  */
31
- static _newFromSoupMessage(message: Soup.Message, type?: Soup.MessageHeadersType): Headers;
35
+ static _newFromSoupMessage(soup: typeof Soup, message: Soup.Message, type?: Soup.MessageHeadersType): Headers;
32
36
  }
33
37
  export {};
package/lib/headers.js CHANGED
@@ -2,7 +2,6 @@
2
2
  // Adapted from node-fetch (https://github.com/node-fetch/node-fetch/blob/main/src/headers.js)
3
3
  // Copyright (c) node-fetch contributors. MIT license.
4
4
  // Modifications: Standalone implementation using internal Map, libsoup integration
5
- import Soup from '@girs/soup-3.0';
6
5
  import { validateHeaderName, validateHeaderValue } from '@gjsify/http/validators';
7
6
  const _headers = Symbol('Headers.headers');
8
7
  function isBoxedPrimitive(val) {
@@ -146,9 +145,13 @@ export default class Headers {
146
145
  }
147
146
  /**
148
147
  * Append all headers to a Soup.Message for sending.
148
+ *
149
+ * `soup` is the lazily-loaded Soup namespace (see utils/soup-lazy.ts); it is
150
+ * passed in rather than statically imported so this module does not link the
151
+ * `gi://Soup` typelib at import time.
149
152
  */
150
- _appendToSoupMessage(message, type = Soup.MessageHeadersType.REQUEST) {
151
- const soupHeaders = message ? message.get_request_headers() : new Soup.MessageHeaders(type);
153
+ _appendToSoupMessage(soup, message, type = soup.MessageHeadersType.REQUEST) {
154
+ const soupHeaders = message ? message.get_request_headers() : new soup.MessageHeaders(type);
152
155
  for (const [name, value] of this.entries()) {
153
156
  soupHeaders.append(name, value);
154
157
  }
@@ -157,10 +160,10 @@ export default class Headers {
157
160
  /**
158
161
  * Create a Headers instance from a Soup.Message's headers.
159
162
  */
160
- static _newFromSoupMessage(message, type = Soup.MessageHeadersType.RESPONSE) {
163
+ static _newFromSoupMessage(soup, message, type = soup.MessageHeadersType.RESPONSE) {
161
164
  const headers = new Headers();
162
165
  let soupHeaders;
163
- if (type === Soup.MessageHeadersType.RESPONSE) {
166
+ if (type === soup.MessageHeadersType.RESPONSE) {
164
167
  soupHeaders = message.get_response_headers();
165
168
  }
166
169
  else {
package/lib/index.js CHANGED
@@ -10,6 +10,7 @@ import { clone } from './body.js';
10
10
  import Response from './response.js';
11
11
  import Headers from './headers.js';
12
12
  import Request, { getSoupRequestOptions } from './request.js';
13
+ import { loadSoup } from './utils/soup-lazy.js';
13
14
  import { FetchError } from './errors/fetch-error.js';
14
15
  import { AbortError } from './errors/abort-error.js';
15
16
  import { isRedirect } from './utils/is-redirect.js';
@@ -146,7 +147,10 @@ export default async function fetch(url, init = {}) {
146
147
  // Error is consumed by the body when read
147
148
  });
148
149
  const message = request._message;
149
- const headers = Headers._newFromSoupMessage(message);
150
+ // Soup is already linked+cached by request._send above; loadSoup() returns
151
+ // the cached namespace so _newFromSoupMessage stays synchronous over it.
152
+ const soup = await loadSoup();
153
+ const headers = Headers._newFromSoupMessage(soup, message);
150
154
  const statusCode = message.status_code;
151
155
  const statusMessage = message.get_reason_phrase();
152
156
  // HTTP fetch step 5 — handle redirects
package/lib/request.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import GLib from '@girs/glib-2.0';
2
- import Soup from '@girs/soup-3.0';
2
+ import type Soup from '@girs/soup-3.0';
3
3
  import Gio from '@girs/gio-2.0';
4
4
  import { URL } from '@gjsify/url';
5
5
  import type { Blob } from './utils/blob-from.js';
package/lib/request.js CHANGED
@@ -3,9 +3,9 @@
3
3
  // Copyright (c) node-fetch contributors. MIT license.
4
4
  // Modifications: Rewritten for GJS using Soup.Message and Gio
5
5
  import GLib from '@girs/glib-2.0';
6
- import Soup from '@girs/soup-3.0';
7
6
  import Gio from '@girs/gio-2.0';
8
7
  import { soupSendAsync, inputStreamToReadable } from './utils/soup-helpers.js';
8
+ import { loadSoup } from './utils/soup-lazy.js';
9
9
  import { URL } from '@gjsify/url';
10
10
  import Headers from './headers.js';
11
11
  import Body, { clone, extractContentType, getTotalBytes } from './body.js';
@@ -85,7 +85,7 @@ function shouldAcceptAnyCert(rejectUnauthorized) {
85
85
  return false;
86
86
  return GLib.getenv('NODE_TLS_REJECT_UNAUTHORIZED') === '0';
87
87
  }
88
- function getSharedSession() {
88
+ function getSharedSession(soup) {
89
89
  if (sharedSession === null) {
90
90
  const maxPerHost = envPositiveInt('GJSIFY_FETCH_MAX_CONNS_PER_HOST', DEFAULT_MAX_CONNS_PER_HOST);
91
91
  // The global ceiling must be ≥ the per-host cap, else libsoup clamps
@@ -93,7 +93,7 @@ function getSharedSession() {
93
93
  const maxConns = Math.max(envPositiveInt('GJSIFY_FETCH_MAX_CONNS', DEFAULT_MAX_CONNS), maxPerHost);
94
94
  // `max-conns` / `max-conns-per-host` are construct-only in libsoup 3 —
95
95
  // they MUST be passed to the constructor; the setters don't exist.
96
- sharedSession = new Soup.Session({
96
+ sharedSession = new soup.Session({
97
97
  maxConns,
98
98
  maxConnsPerHost: maxPerHost,
99
99
  });
@@ -102,7 +102,7 @@ function getSharedSession() {
102
102
  // double-decompression when index.ts also runs DecompressionStream.
103
103
  // Remove it once here so our JS-level decompression handles everything.
104
104
  // (No throw path: an absent feature is a documented silent no-op.)
105
- sharedSession.remove_feature_by_type(Soup.ContentDecoder.$gtype);
105
+ sharedSession.remove_feature_by_type(soup.ContentDecoder.$gtype);
106
106
  }
107
107
  return sharedSession;
108
108
  }
@@ -263,32 +263,11 @@ export class Request extends Body {
263
263
  else {
264
264
  referrer = undefined;
265
265
  }
266
- // Only create Soup objects for HTTP/HTTPS — data: URIs etc. don't go through Soup
267
- const scheme = parsedURL.protocol;
268
- let session = null;
269
- let message = null;
270
- if (scheme === 'http:' || scheme === 'https:') {
271
- // Reuse the process-wide shared session (see getSharedSession docs):
272
- // a per-request session that gets GC-finalized while a connection is
273
- // still registered on its host triggers libsoup's
274
- // `host->conns == NULL` CRITICAL.
275
- session = getSharedSession();
276
- message = new Soup.Message({
277
- method,
278
- // ENCODED, not NONE: `parsedURL.toString()` is already a
279
- // percent-encoded WHATWG URL. Parsing with NONE decodes it a
280
- // second time, collapsing an escaped `%2F` in a path segment to
281
- // a literal `/` — which sends `PUT /@scope/name` instead of the
282
- // required `PUT /@scope%2Fname`. npm's registry tolerates the
283
- // literal slash when UPDATING an existing package but 404s on
284
- // the package-CREATE route and the OIDC token-exchange endpoint
285
- // (`/-/npm/v1/oidc/token/exchange/package/@scope%2Fname`), which
286
- // broke first-publish of new scoped packages + Trusted-Publisher
287
- // OIDC auth once the CLI began running under GJS. ENCODED keeps
288
- // the escaping intact on the wire.
289
- uri: GLib.Uri.parse(parsedURL.toString(), GLib.UriFlags.ENCODED),
290
- });
291
- }
266
+ // Soup objects (session + message) are created lazily on first send
267
+ // (see `_send`), NOT here: constructing a Request — or merely importing
268
+ // `@gjsify/fetch` — must not link the `gi://Soup` typelib, so that a
269
+ // Soup-free command (e.g. `gjsify tsc`) does not require libsoup. Only
270
+ // http:/https: requests ever reach Soup; other schemes never do.
292
271
  this[INTERNALS] = {
293
272
  method,
294
273
  redirect: init?.redirect || inputRL.redirect || 'follow',
@@ -297,8 +276,8 @@ export class Request extends Body {
297
276
  signal,
298
277
  referrer,
299
278
  referrerPolicy: '',
300
- session,
301
- message,
279
+ session: null,
280
+ message: null,
302
281
  };
303
282
  // Node-fetch-only options
304
283
  this.follow =
@@ -322,14 +301,41 @@ export class Request extends Body {
322
301
  * Send the request using Soup.
323
302
  */
324
303
  async _send(options) {
325
- const { session, message, signal, parsedURL } = this[INTERNALS];
326
- if (!session || !message) {
304
+ const { signal, parsedURL } = this[INTERNALS];
305
+ const scheme = parsedURL.protocol;
306
+ if (scheme !== 'http:' && scheme !== 'https:') {
327
307
  throw new Error('Cannot send request: no Soup session (non-HTTP URL?)');
328
308
  }
309
+ // Link the Soup typelib now (dynamic import), deferred from construction
310
+ // so that importing @gjsify/fetch / building a Soup-free command never
311
+ // links libsoup — the typelib is loaded exactly when a request is sent.
312
+ const soup = await loadSoup();
313
+ // Reuse the process-wide shared session (see getSharedSession docs): a
314
+ // per-request session that gets GC-finalized while a connection is still
315
+ // registered on its host triggers libsoup's `host->conns == NULL`
316
+ // CRITICAL.
317
+ const session = getSharedSession(soup);
318
+ const message = new soup.Message({
319
+ method: this[INTERNALS].method,
320
+ // ENCODED, not NONE: `parsedURL.toString()` is already a
321
+ // percent-encoded WHATWG URL. Parsing with NONE decodes it a second
322
+ // time, collapsing an escaped `%2F` in a path segment to a literal
323
+ // `/` — which sends `PUT /@scope/name` instead of the required
324
+ // `PUT /@scope%2Fname`. npm's registry tolerates the literal slash
325
+ // when UPDATING an existing package but 404s on the package-CREATE
326
+ // route and the OIDC token-exchange endpoint
327
+ // (`/-/npm/v1/oidc/token/exchange/package/@scope%2Fname`), which
328
+ // broke first-publish of new scoped packages + Trusted-Publisher
329
+ // OIDC auth once the CLI began running under GJS. ENCODED keeps the
330
+ // escaping intact on the wire.
331
+ uri: GLib.Uri.parse(parsedURL.toString(), GLib.UriFlags.ENCODED),
332
+ });
333
+ this[INTERNALS].session = session;
334
+ this[INTERNALS].message = message;
329
335
  // ContentDecoder is removed once on the shared session in
330
336
  // getSharedSession() (so the Content-Encoding header survives for our
331
337
  // JS-level DecompressionStream in index.ts). Nothing per-request here.
332
- options.headers._appendToSoupMessage(message);
338
+ options.headers._appendToSoupMessage(soup, message);
333
339
  // Attach the request body to the Soup message (needed for POST/PUT/PATCH).
334
340
  // Use _rawBodyBuffer to read the body without consuming the stream (the
335
341
  // `body` getter may have already put the internal Readable into flowing mode,
@@ -33,16 +33,40 @@ export default async () => {
33
33
  const RequestCtor = globalThis.Request;
34
34
  const fetchFn = globalThis.fetch;
35
35
  await describe('@gjsify/fetch — Soup.Session lifecycle (host->conns regression)', async () => {
36
- await it('reuses one shared Soup.Session across HTTP requests (no per-request session)', () => {
36
+ await it('reuses one shared Soup.Session across HTTP requests (no per-request session)', async () => {
37
37
  // Two distinct HTTP Requests must share the very same Soup.Session
38
38
  // instance. A per-request session is what triggered the GC race;
39
39
  // a shared singleton can never be finalized while in use.
40
- const a = new RequestCtor('http://example.com/a');
41
- const b = new RequestCtor('https://example.org/b');
42
- expect(a._session).toBeTruthy();
43
- expect(b._session).toBeTruthy();
44
- expect(a._session === b._session).toBe(true);
45
- expect(a._session instanceof Soup.Session).toBe(true);
40
+ //
41
+ // The session is created lazily on first SEND (deferred from the
42
+ // Request constructor so that importing @gjsify/fetch links no
43
+ // `gi://Soup` typelib — see utils/soup-lazy.ts). So drive two real
44
+ // sends against a local server and assert the invariant where the
45
+ // sessions actually live; `_session` is null before send.
46
+ const server = new Soup.Server({});
47
+ server.add_handler(null, (_s, msg) => {
48
+ msg.set_status(200, null);
49
+ msg.set_response('application/json', Soup.MemoryUse.COPY, new TextEncoder().encode('{}'));
50
+ });
51
+ server.listen_local(0, Soup.ServerListenOptions.IPV4_ONLY);
52
+ const base = server.get_uris()[0].to_string();
53
+ try {
54
+ const a = new RequestCtor(base);
55
+ const b = new RequestCtor(base);
56
+ // Before any send, no Soup object is allocated.
57
+ expect(a._session).toBeNull();
58
+ const ra = await a._send({ headers: a.headers });
59
+ const rb = await b._send({ headers: b.headers });
60
+ ra.readable.resume();
61
+ rb.readable.resume();
62
+ expect(a._session).toBeTruthy();
63
+ expect(b._session).toBeTruthy();
64
+ expect(a._session === b._session).toBe(true);
65
+ expect(a._session instanceof Soup.Session).toBe(true);
66
+ }
67
+ finally {
68
+ server.disconnect();
69
+ }
46
70
  });
47
71
  await it('non-HTTP requests do not allocate a Soup.Session', () => {
48
72
  const dataReq = new RequestCtor('data:text/plain,hi');
@@ -0,0 +1,4 @@
1
+ import type Soup from '@girs/soup-3.0';
2
+ /** Load (once) and return the Soup 3.0 namespace, linking `gi://Soup` on first
3
+ * call rather than at module import. */
4
+ export declare function loadSoup(): Promise<typeof Soup>;
@@ -0,0 +1,6 @@
1
+ let cached = null;
2
+ /** Load (once) and return the Soup 3.0 namespace, linking `gi://Soup` on first
3
+ * call rather than at module import. */
4
+ export async function loadSoup() {
5
+ return (cached ??= (await import('gi://Soup?version=3.0')).default);
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/fetch",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "Web and Node.js fetch module for Gjs",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -51,8 +51,8 @@
51
51
  "fetch"
52
52
  ],
53
53
  "devDependencies": {
54
- "@gjsify/cli": "^0.30.0",
55
- "@gjsify/unit": "^0.30.0",
54
+ "@gjsify/cli": "^0.32.0",
55
+ "@gjsify/unit": "^0.32.0",
56
56
  "@types/node": "^25.9.2",
57
57
  "typescript": "^6.0.3"
58
58
  },
@@ -61,11 +61,11 @@
61
61
  "@girs/gjs": "^4.1.0",
62
62
  "@girs/glib-2.0": "^4.1.0",
63
63
  "@girs/soup-3.0": "^4.1.0",
64
- "@gjsify/dom-events": "^0.30.0",
65
- "@gjsify/formdata": "^0.30.0",
66
- "@gjsify/http": "^0.30.0",
67
- "@gjsify/url": "^0.30.0",
68
- "@gjsify/utils": "^0.30.0"
64
+ "@gjsify/dom-events": "^0.32.0",
65
+ "@gjsify/formdata": "^0.32.0",
66
+ "@gjsify/http": "^0.32.0",
67
+ "@gjsify/url": "^0.32.0",
68
+ "@gjsify/utils": "^0.32.0"
69
69
  },
70
70
  "gjsify": {
71
71
  "runtimes": {