@gjsify/fetch 0.4.36 → 0.4.38

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{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 getSharedSession(){if(g===null){g=new f.Session;try{g.remove_feature_by_type(f.ContentDecoder.$gtype)}catch{}}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.NONE)}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;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.NONE)})),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.referrerPolicy=i?.referrerPolicy||o.referrerPolicy||``}async _send(e){let{session:t,message:n}=this[h];if(!t||!n)throw Error(`Cannot send request: no Soup session (non-HTTP URL?)`);e.headers._appendToSoupMessage(n);let r=this._rawBodyBuffer;if(r!==null&&r.byteLength>0){let t=e.headers.get(`content-type`)||null;n.set_request_body_from_bytes(t,new p.Bytes(r))}let i=new m.Cancellable;return this[h].inputStream=await s(t,n,p.PRIORITY_DEFAULT,i),this[h].readable=o(this[h].inputStream),{inputStream:this[h].inputStream,readable:this[h].readable,cancellable:i}}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{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 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});try{g.remove_feature_by_type(f.ContentDecoder.$gtype)}catch{}}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.NONE)}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;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.NONE)})),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.referrerPolicy=i?.referrerPolicy||o.referrerPolicy||``}async _send(e){let{session:t,message:n}=this[h];if(!t||!n)throw Error(`Cannot send request: no Soup session (non-HTTP URL?)`);e.headers._appendToSoupMessage(n);let r=this._rawBodyBuffer;if(r!==null&&r.byteLength>0){let t=e.headers.get(`content-type`)||null;n.set_request_body_from_bytes(t,new p.Bytes(r))}let i=new m.Cancellable;return this[h].inputStream=await s(t,n,p.PRIORITY_DEFAULT,i),this[h].readable=o(this[h].inputStream),{inputStream:this[h].inputStream,readable:this[h].readable,cancellable:i}}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};
package/lib/request.js CHANGED
@@ -46,9 +46,45 @@ const INTERNALS = Symbol('Request internals');
46
46
  * instantiate a Soup object.
47
47
  */
48
48
  let sharedSession = null;
49
+ /**
50
+ * Connections libsoup may open per host (and overall) on the shared session.
51
+ *
52
+ * libsoup's default `max-conns-per-host` is a conservative **2**, which
53
+ * serialises bulk-fetch workloads to two-at-a-time even over a multiplexed
54
+ * HTTP/2 connection. `@gjsify/npm-registry` (during `gjsify install`) hammers
55
+ * a single host — the npm registry — with hundreds of packument + tarball
56
+ * GETs; at the default cap a 16-wide download pool was measured stalling at
57
+ * ~2 effective in-flight requests (16 packuments in ~3.0s vs ~0.24s once the
58
+ * cap is lifted — a 12× difference, same h2 connection).
59
+ *
60
+ * 16 matches npm's `maxsockets` (15) and pnpm's `network-concurrency` (16)
61
+ * defaults and is browser-competitive (Chrome uses 6 per host for HTTP/1.1,
62
+ * more via h2 stream multiplexing). It is harmless for light fetch users, who
63
+ * never materialise more than a couple of concurrent requests to one host.
64
+ * Override per-host with `GJSIFY_FETCH_MAX_CONNS_PER_HOST` and the global
65
+ * ceiling with `GJSIFY_FETCH_MAX_CONNS`.
66
+ */
67
+ const DEFAULT_MAX_CONNS_PER_HOST = 16;
68
+ const DEFAULT_MAX_CONNS = 64;
69
+ function envPositiveInt(name, fallback) {
70
+ const raw = GLib.getenv(name);
71
+ if (!raw)
72
+ return fallback;
73
+ const n = Number.parseInt(raw, 10);
74
+ return Number.isFinite(n) && n > 0 ? n : fallback;
75
+ }
49
76
  function getSharedSession() {
50
77
  if (sharedSession === null) {
51
- sharedSession = new Soup.Session();
78
+ const maxPerHost = envPositiveInt('GJSIFY_FETCH_MAX_CONNS_PER_HOST', DEFAULT_MAX_CONNS_PER_HOST);
79
+ // The global ceiling must be ≥ the per-host cap, else libsoup clamps
80
+ // the per-host value down to it.
81
+ const maxConns = Math.max(envPositiveInt('GJSIFY_FETCH_MAX_CONNS', DEFAULT_MAX_CONNS), maxPerHost);
82
+ // `max-conns` / `max-conns-per-host` are construct-only in libsoup 3 —
83
+ // they MUST be passed to the constructor; the setters don't exist.
84
+ sharedSession = new Soup.Session({
85
+ maxConns,
86
+ maxConnsPerHost: maxPerHost,
87
+ });
52
88
  // Soup auto-adds a ContentDecoder to new sessions, but it decodes the
53
89
  // body without removing the Content-Encoding header, causing
54
90
  // double-decompression when index.ts also runs DecompressionStream.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/fetch",
3
- "version": "0.4.36",
3
+ "version": "0.4.38",
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,26 +51,27 @@
51
51
  "fetch"
52
52
  ],
53
53
  "devDependencies": {
54
- "@gjsify/cli": "^0.4.36",
55
- "@gjsify/unit": "^0.4.36",
54
+ "@gjsify/cli": "^0.4.38",
55
+ "@gjsify/unit": "^0.4.38",
56
56
  "@types/node": "^25.9.1",
57
- "typescript": "^5.9.3"
57
+ "typescript": "^6.0.3"
58
58
  },
59
59
  "dependencies": {
60
60
  "@girs/gio-2.0": "2.88.0-4.0.4",
61
61
  "@girs/gjs": "4.0.4",
62
62
  "@girs/glib-2.0": "2.88.0-4.0.4",
63
63
  "@girs/soup-3.0": "3.6.6-4.0.4",
64
- "@gjsify/formdata": "^0.4.36",
65
- "@gjsify/http": "^0.4.36",
66
- "@gjsify/url": "^0.4.36",
67
- "@gjsify/utils": "^0.4.36"
64
+ "@gjsify/formdata": "^0.4.38",
65
+ "@gjsify/http": "^0.4.38",
66
+ "@gjsify/url": "^0.4.38",
67
+ "@gjsify/utils": "^0.4.38"
68
68
  },
69
69
  "gjsify": {
70
70
  "runtimes": {
71
71
  "gjs": "polyfill",
72
72
  "node": "native",
73
- "browser": "native"
73
+ "browser": "native",
74
+ "nativescript": "none"
74
75
  }
75
76
  },
76
77
  "license": "MIT",