@bereasoftware/nexa 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.en.md +1288 -0
- package/README.md +1304 -0
- package/dist/nexa.cjs.js +1 -0
- package/dist/nexa.es.js +934 -0
- package/dist/nexa.iife.js +1 -0
- package/dist/nexa.umd.js +1 -0
- package/dist/types/http-client/http-client.d.ts +109 -0
- package/dist/types/http-client/index.d.ts +7 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/types/index.d.ts +142 -0
- package/dist/types/utils/index.d.ts +392 -0
- package/package.json +98 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var Nexa=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=e=>({ok:!0,value:e}),n=e=>({ok:!1,error:e});function r(e){return{validate(r){let i=r;for(let[t,r]of Object.entries(e))if(!r(i[t]))return n({message:`Validation failed: field "${t}" is invalid`,code:`VALIDATION_ERROR`});return t(r)}}}function i(e){return{validate(r){let i=r,a=e.filter(e=>!(e in i));return a.length>0?n({message:`Validation failed: missing fields: ${a.join(`, `)}`,code:`VALIDATION_ERROR`}):t(r)}}}var a={validate(e){return Array.isArray(e)?t(e):n({message:`Expected array response`,code:`VALIDATION_ERROR`})}},o={validate(e){return e&&typeof e==`object`&&!Array.isArray(e)?t(e):n({message:`Expected object response`,code:`VALIDATION_ERROR`})}},s={transform(e){return _(e,h)}},c={transform(e){return _(e,g)}},l={transform(e){return v(e)}};function u(e){return{transform(t){return Array.isArray(t)?t.map(t=>y(t,e)):y(t,e)}}}function d(e){return{transform(t){return{[e]:t}}}}var f=class{maxAttempts;constructor(e=5){this.maxAttempts=e}shouldRetry(e){return e<this.maxAttempts}delayMs(e){return e*50}},p=class{retryableStatuses=[408,429,500,502,503,504];maxAttempts;constructor(e=3){this.maxAttempts=e}shouldRetry(e,t){return e>=this.maxAttempts?!1:this.retryableStatuses.includes(t.status??0)||t.code===`TIMEOUT`}delayMs(e){return Math.min(1e3*2**(e-1),1e4)}},m=class{failureCount=0;lastFailureTime=0;maxAttempts;failureThreshold;resetTimeMs;constructor(e=3,t=5,n=6e4){this.maxAttempts=e,this.failureThreshold=t,this.resetTimeMs=n}shouldRetry(e){return e>=this.maxAttempts||(Date.now()-this.lastFailureTime>this.resetTimeMs&&(this.failureCount=0),this.failureCount>=this.failureThreshold)?!1:(this.failureCount++,this.lastFailureTime=Date.now(),!0)}delayMs(e){return 100*2**(e-1)}reset(){this.failureCount=0,this.lastFailureTime=0}};function h(e){return e.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function g(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}function _(e,t){if(!e||typeof e!=`object`)return e;if(Array.isArray(e))return e.map(e=>_(e,t));let n={};for(let[r,i]of Object.entries(e))n[t(r)]=_(i,t);return n}function v(e,t=``){let n={};if(Array.isArray(e))e.forEach((e,r)=>{let i=t?`${t}[${r}]`:`[${r}]`;Object.assign(n,v(e,i))});else if(e&&typeof e==`object`)for(let[r,i]of Object.entries(e)){let e=t?`${t}.${r}`:r;i&&typeof i==`object`&&!Array.isArray(i)?Object.assign(n,v(i,e)):n[e]=i}return n}function y(e,t){if(!e||typeof e!=`object`)return{};let n=e,r={};for(let e of t)e in n&&(r[e]=n[e]);return r}function b(e){let t=new AbortController,n=setTimeout(()=>t.abort(),e);return t.signal.addEventListener(`abort`,()=>clearTimeout(n),{once:!0}),t}async function x(e,t=3){try{return await e()}catch(n){if(t<=0)throw n;return x(e,t-1)}}var S=class{cache=new Map;get(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),null):t.data:null}set(e,t,n=6e4){this.cache.set(e,{data:t,timestamp:Date.now(),ttlMs:n})}clear(){this.cache.clear()}has(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),!1):!0:!1}delete(e){this.cache.delete(e)}};function C(e={}){let t=e.cache||new S,n=e.ttlMs||6e4,r=e.cacheableStatuses||[200,304];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase(),o=a===`GET`,s=`${a}:${e.request.url}`;if(o&&t.has(s)){let n=t.get(s);if(n){e.response=n,e.state.cacheHit=!0;return}}await i(),o&&e.response&&r.includes(e.response.status)&&(t.set(s,e.response,n),e.state.cacheMiss=!0)}}var ee=C(),w=class{pending=new Map;async execute(e,t){if(this.pending.has(e))return this.pending.get(e);let n=t().finally(()=>{this.pending.delete(e)});return this.pending.set(e,n),n}clear(){this.pending.clear()}};function T(e={}){let t=e.deduplicator||new w,n=e.includeBody??!1,r=e.methods||[`GET`];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase();if(!r.includes(a)){await i();return}let o=`${a}:${e.request.url}`;n&&e.request.body&&(o+=`:${JSON.stringify(e.request.body)}`);try{e.response=await t.execute(o,async()=>(await i(),e.response)),e.state.deduped=!0}catch(t){throw e.error=t,t}}}var E=T();function D(e){return async t=>{let n=-1;async function r(i){if(i<=n)throw Error(`next() called multiple times`);n=i;let a=e[i];a&&await a(t,()=>r(i+1))}await r(0)}}var O=class{middlewares=[];use(e){return this.middlewares.push(e),this}async execute(e){if(e&&typeof e==`object`&&`request`in e&&`response`in e){let t=e;return await D(this.middlewares.map(e=>typeof e==`function`&&e.length===2?e:async(t,n)=>{let r=e;t.response.body=await r(t.response.body),await n()}))(t),t.response.body}let t=e;for(let e of this.middlewares)t=await e(t);return t}clear(){this.middlewares=[]}};function k(e,t,n,r={}){return{ok:e>=200&&e<300,data:t,error:n,status:e,headers:r}}function A(e){return async(t,n)=>{let r=e.path,i;switch(e.method){case`GET`:i=await t.get(r);break;case`POST`:i=await t.post(r,n);break;case`PUT`:i=await t.put(r,n);break;case`PATCH`:i=await t.patch(r,n);break;case`DELETE`:i=await t.delete(r);break;default:throw Error(`Unsupported method: ${e.method}`)}return i}}function j(e){return{request:async(t,n,r)=>{let i=e[n];return await A(i)(t,r)}}}var M=class e{subscribers=[];errorSubscribers=[];completeSubscribers=[];subscribe(e,t,n){return e&&this.subscribers.push(e),t&&this.errorSubscribers.push(t),n&&this.completeSubscribers.push(n),{unsubscribe:()=>{this.subscribers=this.subscribers.filter(t=>t!==e),this.errorSubscribers=this.errorSubscribers.filter(e=>e!==t),this.completeSubscribers=this.completeSubscribers.filter(e=>e!==n)}}}next(e){this.subscribers.forEach(t=>t(e))}error(e){this.errorSubscribers.forEach(t=>t(e))}complete(){this.completeSubscribers.forEach(e=>e())}map(t){let n=new e;return this.subscribe(e=>n.next(t(e)),e=>n.error(e),()=>n.complete()),n}filter(t){let n=new e;return this.subscribe(e=>{t(e)&&n.next(e)},e=>n.error(e),()=>n.complete()),n}};function N(e){return t=>{if(!e(t))throw TypeError(`Value does not match expected type`);return t}}function P(e){return e}function F(e){return P(e)}var I=class{_promise;resolveFunc;rejectFunc;constructor(){this._promise=new Promise((e,t)=>{this.resolveFunc=e,this.rejectFunc=t})}resolve(e){this.resolveFunc(e)}reject(e){this.rejectFunc(e)}get promise(){return this._promise}promise_(){return this._promise}};async function L(e,t={}){let n=e.body?.getReader();if(!n)throw Error(`Response body is not readable`);let r=[],i=0,a=parseInt(e.headers.get(`content-length`)||`0`,10);for(;;){let{done:e,value:o}=await n.read();if(e)break;r.push(o),i+=o.length,t.onChunk&&await t.onChunk(o),t.onProgress&&a>0&&t.onProgress(i,a)}return R(r,i)}function R(e,t){let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.byteLength;return n}async function z(e,t){let n=await L(e);if(typeof window>`u`)await(await import(`fs`).then(e=>e.promises)).writeFile(t,n);else{let e=new Blob([n.buffer],{type:`application/octet-stream`}),r=URL.createObjectURL(e),i=document.createElement(`a`);i.href=r,i.download=t,i.click(),URL.revokeObjectURL(r)}}function B(e={}){return async(t,n)=>{if(await n(),t.response&&t.response.body&&typeof t.response.body==`object`&&`getReader`in t.response.body){let n=t.response.body.getReader(),r=[],i=0,a=parseInt(t.response.headers?.[`content-length`]||`0`,10);try{for(;;){let{done:o,value:s}=await n.read();if(o)break;r.push(s),i+=s.length,e.onChunk&&await e.onChunk(s),e.onProgress&&a>0&&e.onProgress(i,a),t.state.streamedChunks=t.state.streamedChunks||[],t.state.streamedChunks.push(s)}let o=R(r,i);t.response.body=o,t.state.streaming=!0,t.state.streamedBytes=i}finally{n.releaseLock()}}}}var V=B({onProgress:(e,t)=>{if(t>0){let n=Math.round(e/t*100);console.log(`⬇️ Streaming: ${n}% (${e}/${t} bytes)`)}}}),H=class{plugins=[];cache=new S;deduplicator=new w;middlewares=[];listeners=new Map;register(e){return this.plugins.push(e),e.setup(this),this.emit(`plugin:registered`,e.name),this}addMiddleware(e){return this.middlewares.push(e),this}getCache(){return this.cache}getDeduplicator(){return this.deduplicator}getPipeline(){return D(this.middlewares)}async executePipeline(e){await this.getPipeline()(e)}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e)?.add(t),this}emit(e,...t){let n=this.listeners.get(e);if(n)for(let e of n)e(...t)}off(e,t){return this.listeners.get(e)?.delete(t),this}getPlugins(){return[...this.plugins]}clear(){this.plugins=[],this.middlewares=[],this.listeners.clear(),this.cache.clear(),this.deduplicator.clear()}},U={name:`logger`,setup(e){e.on(`request:start`,(...e)=>{let t=e[0];console.log(`📤 Request started: ${t}`)}),e.on(`request:success`,(...e)=>{let t=e[0],n=e[1];console.log(`✅ Request succeeded: ${t} (${n})`)}),e.on(`request:error`,(...e)=>{let t=e[0],n=e[1];console.error(`❌ Request failed: ${t}`,n)})}},W=class{name=`metrics`;metrics={requests:0,errors:0,totalTime:0,avgTime:0};setup(e){e.on(`request:complete`,(...e)=>{let t=e[0],n=e[1];this.metrics.requests++,this.metrics.totalTime+=t,this.metrics.avgTime=this.metrics.totalTime/this.metrics.requests,n||this.metrics.errors++})}getMetrics(){return{...this.metrics}}},G=class{name=`cache`;ttlMs;constructor(e=6e4){this.ttlMs=e}setup(e){e.addMiddleware(C({ttlMs:this.ttlMs}))}},K=class{name=`dedupe`;setup(e){e.addMiddleware(T())}},q=class{store=new S;get(e){return this.store.get(e)}set(e,t,n=6e4){this.store.set(e,t,n)}has(e){return this.store.has(e)}clear(){this.store.clear()}},J=class{maxAttempts;baseDelayMs;constructor(e=3,t=100){this.maxAttempts=e,this.baseDelayMs=t}shouldRetry(e,t){let n=t.status!==void 0&&t.status>=500,r=t.code===`NETWORK_ERROR`;return e<this.maxAttempts&&(n||r||t.code===`TIMEOUT`)}delayMs(e){let t=this.baseDelayMs*2**(e-1),n=Math.random()*t*.1;return Math.min(t+n,3e4)}},Y=class{running=0;queue=[];maxConcurrent;constructor(e){this.maxConcurrent=e}async acquire(){if(this.running<this.maxConcurrent){this.running++;return}return new Promise(e=>{this.queue.push(()=>{this.running++,e()})})}release(){this.running--;let e=this.queue.shift();e&&e()}get pending(){return this.queue.length}get active(){return this.running}};function X(e){return e==null?{serialized:void 0,contentType:null}:typeof FormData<`u`&&e instanceof FormData?{serialized:e,contentType:null}:typeof URLSearchParams<`u`&&e instanceof URLSearchParams?{serialized:e,contentType:`application/x-www-form-urlencoded`}:typeof Blob<`u`&&e instanceof Blob?{serialized:e,contentType:e.type||`application/octet-stream`}:e instanceof ArrayBuffer||ArrayBuffer.isView(e)||typeof ReadableStream<`u`&&e instanceof ReadableStream?{serialized:e,contentType:`application/octet-stream`}:typeof e==`string`?{serialized:e,contentType:`text/plain`}:{serialized:JSON.stringify(e),contentType:`application/json`}}function Z(e,t){return t?e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(e,n)=>{let r=t[n];if(r===void 0)throw Error(`Missing path parameter: :${n}`);return encodeURIComponent(String(r))}):e}var Q=class e{requestInterceptors=[];responseInterceptors=[];cache;config;requestQueue;pendingRequests=new Map;constructor(e={}){this.config={baseURL:e.baseURL??``,defaultHeaders:e.defaultHeaders??{"Content-Type":`application/json`},defaultTimeout:e.defaultTimeout??3e4,validateStatus:e.validateStatus??(e=>e>=200&&e<300),cacheStrategy:e.cacheStrategy??new q,maxConcurrent:e.maxConcurrent??0,defaultResponseType:e.defaultResponseType??`auto`,defaultHooks:e.defaultHooks??{}},this.cache=this.config.cacheStrategy,this.requestQueue=this.config.maxConcurrent>0?new Y(this.config.maxConcurrent):null}async request(e){let r={...this.config.defaultHooks,...e.hooks},i=this.getMaxAttempts(e.retry),a=this.getRetryStrategy(e.retry),o=Symbol(`request`);r.onStart?.(this.buildRequest(e)),this.requestQueue&&await this.requestQueue.acquire();try{for(let s=1;s<=i;s++)try{if((e.method===`GET`||!e.method)&&e.cache?.enabled){let n=this.getCacheKey(e),i=this.cache.get(n);if(i){let e=i;return r.onSuccess?.(e),r.onFinally?.(),t(e)}}let n=this.buildRequest(e);for(let e of this.requestInterceptors)n=await e.onRequest(n);let i=new AbortController;this.pendingRequests.set(o,i),e.signal&&e.signal.addEventListener(`abort`,()=>i.abort(),{once:!0});let a=performance.now(),s=await this.fetchWithTimeout(n,e.timeout??this.config.defaultTimeout,i),c=performance.now()-a,l=s;e.onDownloadProgress&&s.body&&(l=this.trackDownloadProgress(s,e.onDownloadProgress));let u=e.responseType??this.config.defaultResponseType,d=await this.parseResponse(l,n,c,u);if(!this.config.validateStatus(d.status))throw{message:`Request failed with status ${d.status}`,status:d.status,statusText:d.statusText,code:`HTTP_ERROR`};if(e.validate){let t=e.validate.validate(d.data);if(!t.ok)return t}e.transform&&(d.data=e.transform.transform(d.data));let f=d;for(let e of this.responseInterceptors)f=await e.onResponse(f);if((e.method===`GET`||!e.method)&&e.cache?.enabled){let t=this.getCacheKey(e);this.cache.set(t,f,e.cache.ttlMs)}return r.onSuccess?.(f),this.pendingRequests.delete(o),t(f)}catch(e){let t=this.isHttpErrorDetails(e)?e:this.normalizeError(e);if(s<i&&a.shouldRetry(s,t)){r.onRetry?.(s,t);let e=a.delayMs(s);await this.delay(e);continue}let c=t;for(let e of this.responseInterceptors)e.onError&&(c=await e.onError(c));return r.onError?.(c),this.pendingRequests.delete(o),n(c)}let s={message:`Max retries exceeded`,code:`MAX_RETRIES`};return r.onError?.(s),n(s)}finally{r.onFinally?.(),this.requestQueue&&this.requestQueue.release()}}get(e,t){return this.request({...t,url:e,method:`GET`})}post(e,t,n){return this.request({...n,url:e,method:`POST`,body:t})}put(e,t,n){return this.request({...n,url:e,method:`PUT`,body:t})}patch(e,t,n){return this.request({...n,url:e,method:`PATCH`,body:t})}delete(e,t){return this.request({...t,url:e,method:`DELETE`})}head(e,t){return this.request({...t,url:e,method:`HEAD`})}options(e,t){return this.request({...t,url:e,method:`OPTIONS`})}addRequestInterceptor(e){return this.requestInterceptors.push(e),()=>{let t=this.requestInterceptors.indexOf(e);t!==-1&&this.requestInterceptors.splice(t,1)}}addResponseInterceptor(e){return this.responseInterceptors.push(e),()=>{let t=this.responseInterceptors.indexOf(e);t!==-1&&this.responseInterceptors.splice(t,1)}}clearInterceptors(){this.requestInterceptors=[],this.responseInterceptors=[]}clearCache(){this.cache.clear()}cancelAll(){for(let e of this.pendingRequests.values())e.abort();this.pendingRequests.clear()}get activeRequests(){return this.pendingRequests.size}get queueStats(){return{active:this.requestQueue?.active??this.pendingRequests.size,pending:this.requestQueue?.pending??0}}extend(t={}){let n=new e({baseURL:t.baseURL??this.config.baseURL,defaultHeaders:{...this.config.defaultHeaders,...t.defaultHeaders},defaultTimeout:t.defaultTimeout??this.config.defaultTimeout,validateStatus:t.validateStatus??this.config.validateStatus,cacheStrategy:t.cacheStrategy??this.cache,maxConcurrent:t.maxConcurrent??this.config.maxConcurrent,defaultResponseType:t.defaultResponseType??this.config.defaultResponseType,defaultHooks:{...this.config.defaultHooks,...t.defaultHooks}});for(let e of this.requestInterceptors)n.addRequestInterceptor(e);for(let e of this.responseInterceptors)n.addResponseInterceptor(e);return n}async*paginate(e,t,n={}){let r={...n};for(;;){let n=await this.get(e,r);if(!n.ok)break;yield t.getItems(n.value.data);let i=t.getNextPage(n.value.data,r);if(!i)break;r=i}}async poll(e,t,r={}){let i=t.maxAttempts??0;for(let n=1;i===0||n<=i;n++){let a=await this.get(e,r);if(!a.ok||(t.onPoll?.(a.value.data,n),t.until(a.value.data)))return a;if(i>0&&n>=i)break;await this.delay(t.intervalMs)}return n({message:`Polling exhausted after ${i} attempts`,code:`POLL_EXHAUSTED`})}buildRequest(e){let t=Z(e.url,e.params);return{url:this.buildUrl(t,e.query),method:e.method??`GET`,headers:{...this.config.defaultHeaders,...e.headers},body:e.body,params:e.params}}buildUrl(e,t){let n=this.config.baseURL+e;if(t&&Object.keys(t).length>0){let e=new URLSearchParams;Object.entries(t).forEach(([t,n])=>{e.append(t,String(n))}),n+=`?${e.toString()}`}return n}getCacheKey(e){let t=Z(e.url,e.params),n=e.query?JSON.stringify(e.query):``;return`${e.method??`GET`}:${t}${n?`:`+n:``}`}fetchWithTimeout(e,t,n){let{serialized:r,contentType:i}=X(e.body),a={...e.headers};return i?a[`Content-Type`]=i:i===null&&r instanceof FormData&&delete a[`Content-Type`],new Promise((i,o)=>{let s=setTimeout(()=>{n.abort();let e=Error(`Request timed out`);e.name=`TimeoutError`,o(e)},t);fetch(e.url,{method:e.method,headers:a,body:r,signal:n.signal}).then(e=>{clearTimeout(s),i(e)},e=>{clearTimeout(s),o(e)})})}trackDownloadProgress(e,t){let n=parseInt(e.headers.get(`content-length`)||`0`,10),r=e.body?.getReader();if(!r)return e;let i=0,a=new ReadableStream({async pull(e){let{done:a,value:o}=await r.read();if(a){e.close();return}i+=o.byteLength,t({loaded:i,total:n,percent:n>0?Math.round(i/n*100):0}),e.enqueue(o)}});return new Response(a,{headers:e.headers,status:e.status,statusText:e.statusText})}async parseResponse(e,t,n,r){let i=await this.parseBody(e,r);return{status:e.status,statusText:e.statusText,headers:e.headers,data:i,request:t,duration:n}}async parseBody(e,t){switch(t){case`json`:return await e.json();case`text`:return await e.text();case`blob`:return await e.blob();case`arrayBuffer`:return await e.arrayBuffer();case`formData`:return await e.formData();case`stream`:return e.body;default:{let t=e.headers.get(`content-type`)??``;if(t.includes(`application/json`))return await e.json();if(t.includes(`text/`))return await e.text();if(t.includes(`multipart/form-data`))return await e.formData();if(t.includes(`application/octet-stream`)||t.includes(`image/`)||t.includes(`audio/`)||t.includes(`video/`))return await e.blob();try{return await e.json()}catch{return await e.text()}}}}normalizeError(e){return e instanceof Error&&e.name===`TimeoutError`?{message:`Request timed out`,code:`TIMEOUT`}:e instanceof DOMException&&e.name===`AbortError`?{message:`Request aborted`,code:`ABORTED`}:e instanceof Error?e.name===`AbortError`||e.message.includes(`abort`)?{message:`Request aborted`,code:`ABORTED`}:{message:e.message,code:e.name===`TypeError`?`NETWORK_ERROR`:`UNKNOWN_ERROR`,originalError:e}:{message:String(e),code:`UNKNOWN_ERROR`,originalError:e}}isHttpErrorDetails(e){return typeof e==`object`&&!!e&&`message`in e&&`code`in e}getMaxAttempts(e){return e?`maxAttempts`in e?e.maxAttempts:100:1}getRetryStrategy(e){return e?`shouldRetry`in e?e:new J(e.maxAttempts,e.backoffMs):{shouldRetry:()=>!1,delayMs:()=>0}}delay(e){return new Promise(t=>setTimeout(t,e))}},$=class extends Error{status;code;response;constructor(e,t,n,r){super(e),this.name=`HttpError`,this.status=t,this.code=n,this.response=r}};function te(e){return e instanceof $}function ne(e){return new Q(e)}return e.AggressiveRetry=f,e.CachePlugin=G,e.CacheStore=S,e.CircuitBreakerRetry=m,e.ConservativeRetry=p,e.DedupePlugin=K,e.Defer=I,e.Err=n,e.HttpClient=Q,e.HttpError=$,e.LoggerPlugin=U,e.MetricsPlugin=W,e.MiddlewarePipeline=O,e.Ok=t,e.PluginManager=H,e.RequestDeduplicator=w,e.TypedObservable=M,e.cacheMiddleware=ee,e.createApiUrl=F,e.createCacheMiddleware=C,e.createDedupeMiddleware=T,e.createHttpClient=ne,e.createPipeline=D,e.createProjectionTransformer=u,e.createRequiredFieldsValidator=i,e.createSchemaValidator=r,e.createStreamingMiddleware=B,e.createTypeGuard=N,e.createTypedApiClient=j,e.createTypedRequest=A,e.createTypedResponse=k,e.createUrl=P,e.createWrapperTransformer=d,e.dedupeMiddleware=E,e.handleStream=L,e.isHttpError=te,e.retry=x,e.streamToFile=z,e.streamingMiddleware=V,e.transformCamelToSnake=c,e.transformFlatten=l,e.transformSnakeToCamel=s,e.validatorIsArray=a,e.validatorIsObject=o,e.withTimeout=b,e})({});
|
package/dist/nexa.umd.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.Nexa={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=e=>({ok:!0,value:e}),n=e=>({ok:!1,error:e});function r(e){return{validate(r){let i=r;for(let[t,r]of Object.entries(e))if(!r(i[t]))return n({message:`Validation failed: field "${t}" is invalid`,code:`VALIDATION_ERROR`});return t(r)}}}function i(e){return{validate(r){let i=r,a=e.filter(e=>!(e in i));return a.length>0?n({message:`Validation failed: missing fields: ${a.join(`, `)}`,code:`VALIDATION_ERROR`}):t(r)}}}var a={validate(e){return Array.isArray(e)?t(e):n({message:`Expected array response`,code:`VALIDATION_ERROR`})}},o={validate(e){return e&&typeof e==`object`&&!Array.isArray(e)?t(e):n({message:`Expected object response`,code:`VALIDATION_ERROR`})}},s={transform(e){return _(e,h)}},c={transform(e){return _(e,g)}},l={transform(e){return v(e)}};function u(e){return{transform(t){return Array.isArray(t)?t.map(t=>y(t,e)):y(t,e)}}}function d(e){return{transform(t){return{[e]:t}}}}var f=class{maxAttempts;constructor(e=5){this.maxAttempts=e}shouldRetry(e){return e<this.maxAttempts}delayMs(e){return e*50}},p=class{retryableStatuses=[408,429,500,502,503,504];maxAttempts;constructor(e=3){this.maxAttempts=e}shouldRetry(e,t){return e>=this.maxAttempts?!1:this.retryableStatuses.includes(t.status??0)||t.code===`TIMEOUT`}delayMs(e){return Math.min(1e3*2**(e-1),1e4)}},m=class{failureCount=0;lastFailureTime=0;maxAttempts;failureThreshold;resetTimeMs;constructor(e=3,t=5,n=6e4){this.maxAttempts=e,this.failureThreshold=t,this.resetTimeMs=n}shouldRetry(e){return e>=this.maxAttempts||(Date.now()-this.lastFailureTime>this.resetTimeMs&&(this.failureCount=0),this.failureCount>=this.failureThreshold)?!1:(this.failureCount++,this.lastFailureTime=Date.now(),!0)}delayMs(e){return 100*2**(e-1)}reset(){this.failureCount=0,this.lastFailureTime=0}};function h(e){return e.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function g(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}function _(e,t){if(!e||typeof e!=`object`)return e;if(Array.isArray(e))return e.map(e=>_(e,t));let n={};for(let[r,i]of Object.entries(e))n[t(r)]=_(i,t);return n}function v(e,t=``){let n={};if(Array.isArray(e))e.forEach((e,r)=>{let i=t?`${t}[${r}]`:`[${r}]`;Object.assign(n,v(e,i))});else if(e&&typeof e==`object`)for(let[r,i]of Object.entries(e)){let e=t?`${t}.${r}`:r;i&&typeof i==`object`&&!Array.isArray(i)?Object.assign(n,v(i,e)):n[e]=i}return n}function y(e,t){if(!e||typeof e!=`object`)return{};let n=e,r={};for(let e of t)e in n&&(r[e]=n[e]);return r}function b(e){let t=new AbortController,n=setTimeout(()=>t.abort(),e);return t.signal.addEventListener(`abort`,()=>clearTimeout(n),{once:!0}),t}async function x(e,t=3){try{return await e()}catch(n){if(t<=0)throw n;return x(e,t-1)}}var S=class{cache=new Map;get(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),null):t.data:null}set(e,t,n=6e4){this.cache.set(e,{data:t,timestamp:Date.now(),ttlMs:n})}clear(){this.cache.clear()}has(e){let t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttlMs?(this.cache.delete(e),!1):!0:!1}delete(e){this.cache.delete(e)}};function C(e={}){let t=e.cache||new S,n=e.ttlMs||6e4,r=e.cacheableStatuses||[200,304];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase(),o=a===`GET`,s=`${a}:${e.request.url}`;if(o&&t.has(s)){let n=t.get(s);if(n){e.response=n,e.state.cacheHit=!0;return}}await i(),o&&e.response&&r.includes(e.response.status)&&(t.set(s,e.response,n),e.state.cacheMiss=!0)}}var ee=C(),w=class{pending=new Map;async execute(e,t){if(this.pending.has(e))return this.pending.get(e);let n=t().finally(()=>{this.pending.delete(e)});return this.pending.set(e,n),n}clear(){this.pending.clear()}};function T(e={}){let t=e.deduplicator||new w,n=e.includeBody??!1,r=e.methods||[`GET`];return async(e,i)=>{let a=(e.request.method||`GET`).toUpperCase();if(!r.includes(a)){await i();return}let o=`${a}:${e.request.url}`;n&&e.request.body&&(o+=`:${JSON.stringify(e.request.body)}`);try{e.response=await t.execute(o,async()=>(await i(),e.response)),e.state.deduped=!0}catch(t){throw e.error=t,t}}}var E=T();function D(e){return async t=>{let n=-1;async function r(i){if(i<=n)throw Error(`next() called multiple times`);n=i;let a=e[i];a&&await a(t,()=>r(i+1))}await r(0)}}var O=class{middlewares=[];use(e){return this.middlewares.push(e),this}async execute(e){if(e&&typeof e==`object`&&`request`in e&&`response`in e){let t=e;return await D(this.middlewares.map(e=>typeof e==`function`&&e.length===2?e:async(t,n)=>{let r=e;t.response.body=await r(t.response.body),await n()}))(t),t.response.body}let t=e;for(let e of this.middlewares)t=await e(t);return t}clear(){this.middlewares=[]}};function k(e,t,n,r={}){return{ok:e>=200&&e<300,data:t,error:n,status:e,headers:r}}function A(e){return async(t,n)=>{let r=e.path,i;switch(e.method){case`GET`:i=await t.get(r);break;case`POST`:i=await t.post(r,n);break;case`PUT`:i=await t.put(r,n);break;case`PATCH`:i=await t.patch(r,n);break;case`DELETE`:i=await t.delete(r);break;default:throw Error(`Unsupported method: ${e.method}`)}return i}}function j(e){return{request:async(t,n,r)=>{let i=e[n];return await A(i)(t,r)}}}var M=class e{subscribers=[];errorSubscribers=[];completeSubscribers=[];subscribe(e,t,n){return e&&this.subscribers.push(e),t&&this.errorSubscribers.push(t),n&&this.completeSubscribers.push(n),{unsubscribe:()=>{this.subscribers=this.subscribers.filter(t=>t!==e),this.errorSubscribers=this.errorSubscribers.filter(e=>e!==t),this.completeSubscribers=this.completeSubscribers.filter(e=>e!==n)}}}next(e){this.subscribers.forEach(t=>t(e))}error(e){this.errorSubscribers.forEach(t=>t(e))}complete(){this.completeSubscribers.forEach(e=>e())}map(t){let n=new e;return this.subscribe(e=>n.next(t(e)),e=>n.error(e),()=>n.complete()),n}filter(t){let n=new e;return this.subscribe(e=>{t(e)&&n.next(e)},e=>n.error(e),()=>n.complete()),n}};function N(e){return t=>{if(!e(t))throw TypeError(`Value does not match expected type`);return t}}function P(e){return e}function F(e){return P(e)}var I=class{_promise;resolveFunc;rejectFunc;constructor(){this._promise=new Promise((e,t)=>{this.resolveFunc=e,this.rejectFunc=t})}resolve(e){this.resolveFunc(e)}reject(e){this.rejectFunc(e)}get promise(){return this._promise}promise_(){return this._promise}};async function L(e,t={}){let n=e.body?.getReader();if(!n)throw Error(`Response body is not readable`);let r=[],i=0,a=parseInt(e.headers.get(`content-length`)||`0`,10);for(;;){let{done:e,value:o}=await n.read();if(e)break;r.push(o),i+=o.length,t.onChunk&&await t.onChunk(o),t.onProgress&&a>0&&t.onProgress(i,a)}return R(r,i)}function R(e,t){let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.byteLength;return n}async function z(e,t){let n=await L(e);if(typeof window>`u`)await(await import(`fs`).then(e=>e.promises)).writeFile(t,n);else{let e=new Blob([n.buffer],{type:`application/octet-stream`}),r=URL.createObjectURL(e),i=document.createElement(`a`);i.href=r,i.download=t,i.click(),URL.revokeObjectURL(r)}}function B(e={}){return async(t,n)=>{if(await n(),t.response&&t.response.body&&typeof t.response.body==`object`&&`getReader`in t.response.body){let n=t.response.body.getReader(),r=[],i=0,a=parseInt(t.response.headers?.[`content-length`]||`0`,10);try{for(;;){let{done:o,value:s}=await n.read();if(o)break;r.push(s),i+=s.length,e.onChunk&&await e.onChunk(s),e.onProgress&&a>0&&e.onProgress(i,a),t.state.streamedChunks=t.state.streamedChunks||[],t.state.streamedChunks.push(s)}let o=R(r,i);t.response.body=o,t.state.streaming=!0,t.state.streamedBytes=i}finally{n.releaseLock()}}}}var V=B({onProgress:(e,t)=>{if(t>0){let n=Math.round(e/t*100);console.log(`⬇️ Streaming: ${n}% (${e}/${t} bytes)`)}}}),H=class{plugins=[];cache=new S;deduplicator=new w;middlewares=[];listeners=new Map;register(e){return this.plugins.push(e),e.setup(this),this.emit(`plugin:registered`,e.name),this}addMiddleware(e){return this.middlewares.push(e),this}getCache(){return this.cache}getDeduplicator(){return this.deduplicator}getPipeline(){return D(this.middlewares)}async executePipeline(e){await this.getPipeline()(e)}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e)?.add(t),this}emit(e,...t){let n=this.listeners.get(e);if(n)for(let e of n)e(...t)}off(e,t){return this.listeners.get(e)?.delete(t),this}getPlugins(){return[...this.plugins]}clear(){this.plugins=[],this.middlewares=[],this.listeners.clear(),this.cache.clear(),this.deduplicator.clear()}},U={name:`logger`,setup(e){e.on(`request:start`,(...e)=>{let t=e[0];console.log(`📤 Request started: ${t}`)}),e.on(`request:success`,(...e)=>{let t=e[0],n=e[1];console.log(`✅ Request succeeded: ${t} (${n})`)}),e.on(`request:error`,(...e)=>{let t=e[0],n=e[1];console.error(`❌ Request failed: ${t}`,n)})}},W=class{name=`metrics`;metrics={requests:0,errors:0,totalTime:0,avgTime:0};setup(e){e.on(`request:complete`,(...e)=>{let t=e[0],n=e[1];this.metrics.requests++,this.metrics.totalTime+=t,this.metrics.avgTime=this.metrics.totalTime/this.metrics.requests,n||this.metrics.errors++})}getMetrics(){return{...this.metrics}}},G=class{name=`cache`;ttlMs;constructor(e=6e4){this.ttlMs=e}setup(e){e.addMiddleware(C({ttlMs:this.ttlMs}))}},K=class{name=`dedupe`;setup(e){e.addMiddleware(T())}},q=class{store=new S;get(e){return this.store.get(e)}set(e,t,n=6e4){this.store.set(e,t,n)}has(e){return this.store.has(e)}clear(){this.store.clear()}},J=class{maxAttempts;baseDelayMs;constructor(e=3,t=100){this.maxAttempts=e,this.baseDelayMs=t}shouldRetry(e,t){let n=t.status!==void 0&&t.status>=500,r=t.code===`NETWORK_ERROR`;return e<this.maxAttempts&&(n||r||t.code===`TIMEOUT`)}delayMs(e){let t=this.baseDelayMs*2**(e-1),n=Math.random()*t*.1;return Math.min(t+n,3e4)}},Y=class{running=0;queue=[];maxConcurrent;constructor(e){this.maxConcurrent=e}async acquire(){if(this.running<this.maxConcurrent){this.running++;return}return new Promise(e=>{this.queue.push(()=>{this.running++,e()})})}release(){this.running--;let e=this.queue.shift();e&&e()}get pending(){return this.queue.length}get active(){return this.running}};function X(e){return e==null?{serialized:void 0,contentType:null}:typeof FormData<`u`&&e instanceof FormData?{serialized:e,contentType:null}:typeof URLSearchParams<`u`&&e instanceof URLSearchParams?{serialized:e,contentType:`application/x-www-form-urlencoded`}:typeof Blob<`u`&&e instanceof Blob?{serialized:e,contentType:e.type||`application/octet-stream`}:e instanceof ArrayBuffer||ArrayBuffer.isView(e)||typeof ReadableStream<`u`&&e instanceof ReadableStream?{serialized:e,contentType:`application/octet-stream`}:typeof e==`string`?{serialized:e,contentType:`text/plain`}:{serialized:JSON.stringify(e),contentType:`application/json`}}function Z(e,t){return t?e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(e,n)=>{let r=t[n];if(r===void 0)throw Error(`Missing path parameter: :${n}`);return encodeURIComponent(String(r))}):e}var Q=class e{requestInterceptors=[];responseInterceptors=[];cache;config;requestQueue;pendingRequests=new Map;constructor(e={}){this.config={baseURL:e.baseURL??``,defaultHeaders:e.defaultHeaders??{"Content-Type":`application/json`},defaultTimeout:e.defaultTimeout??3e4,validateStatus:e.validateStatus??(e=>e>=200&&e<300),cacheStrategy:e.cacheStrategy??new q,maxConcurrent:e.maxConcurrent??0,defaultResponseType:e.defaultResponseType??`auto`,defaultHooks:e.defaultHooks??{}},this.cache=this.config.cacheStrategy,this.requestQueue=this.config.maxConcurrent>0?new Y(this.config.maxConcurrent):null}async request(e){let r={...this.config.defaultHooks,...e.hooks},i=this.getMaxAttempts(e.retry),a=this.getRetryStrategy(e.retry),o=Symbol(`request`);r.onStart?.(this.buildRequest(e)),this.requestQueue&&await this.requestQueue.acquire();try{for(let s=1;s<=i;s++)try{if((e.method===`GET`||!e.method)&&e.cache?.enabled){let n=this.getCacheKey(e),i=this.cache.get(n);if(i){let e=i;return r.onSuccess?.(e),r.onFinally?.(),t(e)}}let n=this.buildRequest(e);for(let e of this.requestInterceptors)n=await e.onRequest(n);let i=new AbortController;this.pendingRequests.set(o,i),e.signal&&e.signal.addEventListener(`abort`,()=>i.abort(),{once:!0});let a=performance.now(),s=await this.fetchWithTimeout(n,e.timeout??this.config.defaultTimeout,i),c=performance.now()-a,l=s;e.onDownloadProgress&&s.body&&(l=this.trackDownloadProgress(s,e.onDownloadProgress));let u=e.responseType??this.config.defaultResponseType,d=await this.parseResponse(l,n,c,u);if(!this.config.validateStatus(d.status))throw{message:`Request failed with status ${d.status}`,status:d.status,statusText:d.statusText,code:`HTTP_ERROR`};if(e.validate){let t=e.validate.validate(d.data);if(!t.ok)return t}e.transform&&(d.data=e.transform.transform(d.data));let f=d;for(let e of this.responseInterceptors)f=await e.onResponse(f);if((e.method===`GET`||!e.method)&&e.cache?.enabled){let t=this.getCacheKey(e);this.cache.set(t,f,e.cache.ttlMs)}return r.onSuccess?.(f),this.pendingRequests.delete(o),t(f)}catch(e){let t=this.isHttpErrorDetails(e)?e:this.normalizeError(e);if(s<i&&a.shouldRetry(s,t)){r.onRetry?.(s,t);let e=a.delayMs(s);await this.delay(e);continue}let c=t;for(let e of this.responseInterceptors)e.onError&&(c=await e.onError(c));return r.onError?.(c),this.pendingRequests.delete(o),n(c)}let s={message:`Max retries exceeded`,code:`MAX_RETRIES`};return r.onError?.(s),n(s)}finally{r.onFinally?.(),this.requestQueue&&this.requestQueue.release()}}get(e,t){return this.request({...t,url:e,method:`GET`})}post(e,t,n){return this.request({...n,url:e,method:`POST`,body:t})}put(e,t,n){return this.request({...n,url:e,method:`PUT`,body:t})}patch(e,t,n){return this.request({...n,url:e,method:`PATCH`,body:t})}delete(e,t){return this.request({...t,url:e,method:`DELETE`})}head(e,t){return this.request({...t,url:e,method:`HEAD`})}options(e,t){return this.request({...t,url:e,method:`OPTIONS`})}addRequestInterceptor(e){return this.requestInterceptors.push(e),()=>{let t=this.requestInterceptors.indexOf(e);t!==-1&&this.requestInterceptors.splice(t,1)}}addResponseInterceptor(e){return this.responseInterceptors.push(e),()=>{let t=this.responseInterceptors.indexOf(e);t!==-1&&this.responseInterceptors.splice(t,1)}}clearInterceptors(){this.requestInterceptors=[],this.responseInterceptors=[]}clearCache(){this.cache.clear()}cancelAll(){for(let e of this.pendingRequests.values())e.abort();this.pendingRequests.clear()}get activeRequests(){return this.pendingRequests.size}get queueStats(){return{active:this.requestQueue?.active??this.pendingRequests.size,pending:this.requestQueue?.pending??0}}extend(t={}){let n=new e({baseURL:t.baseURL??this.config.baseURL,defaultHeaders:{...this.config.defaultHeaders,...t.defaultHeaders},defaultTimeout:t.defaultTimeout??this.config.defaultTimeout,validateStatus:t.validateStatus??this.config.validateStatus,cacheStrategy:t.cacheStrategy??this.cache,maxConcurrent:t.maxConcurrent??this.config.maxConcurrent,defaultResponseType:t.defaultResponseType??this.config.defaultResponseType,defaultHooks:{...this.config.defaultHooks,...t.defaultHooks}});for(let e of this.requestInterceptors)n.addRequestInterceptor(e);for(let e of this.responseInterceptors)n.addResponseInterceptor(e);return n}async*paginate(e,t,n={}){let r={...n};for(;;){let n=await this.get(e,r);if(!n.ok)break;yield t.getItems(n.value.data);let i=t.getNextPage(n.value.data,r);if(!i)break;r=i}}async poll(e,t,r={}){let i=t.maxAttempts??0;for(let n=1;i===0||n<=i;n++){let a=await this.get(e,r);if(!a.ok||(t.onPoll?.(a.value.data,n),t.until(a.value.data)))return a;if(i>0&&n>=i)break;await this.delay(t.intervalMs)}return n({message:`Polling exhausted after ${i} attempts`,code:`POLL_EXHAUSTED`})}buildRequest(e){let t=Z(e.url,e.params);return{url:this.buildUrl(t,e.query),method:e.method??`GET`,headers:{...this.config.defaultHeaders,...e.headers},body:e.body,params:e.params}}buildUrl(e,t){let n=this.config.baseURL+e;if(t&&Object.keys(t).length>0){let e=new URLSearchParams;Object.entries(t).forEach(([t,n])=>{e.append(t,String(n))}),n+=`?${e.toString()}`}return n}getCacheKey(e){let t=Z(e.url,e.params),n=e.query?JSON.stringify(e.query):``;return`${e.method??`GET`}:${t}${n?`:`+n:``}`}fetchWithTimeout(e,t,n){let{serialized:r,contentType:i}=X(e.body),a={...e.headers};return i?a[`Content-Type`]=i:i===null&&r instanceof FormData&&delete a[`Content-Type`],new Promise((i,o)=>{let s=setTimeout(()=>{n.abort();let e=Error(`Request timed out`);e.name=`TimeoutError`,o(e)},t);fetch(e.url,{method:e.method,headers:a,body:r,signal:n.signal}).then(e=>{clearTimeout(s),i(e)},e=>{clearTimeout(s),o(e)})})}trackDownloadProgress(e,t){let n=parseInt(e.headers.get(`content-length`)||`0`,10),r=e.body?.getReader();if(!r)return e;let i=0,a=new ReadableStream({async pull(e){let{done:a,value:o}=await r.read();if(a){e.close();return}i+=o.byteLength,t({loaded:i,total:n,percent:n>0?Math.round(i/n*100):0}),e.enqueue(o)}});return new Response(a,{headers:e.headers,status:e.status,statusText:e.statusText})}async parseResponse(e,t,n,r){let i=await this.parseBody(e,r);return{status:e.status,statusText:e.statusText,headers:e.headers,data:i,request:t,duration:n}}async parseBody(e,t){switch(t){case`json`:return await e.json();case`text`:return await e.text();case`blob`:return await e.blob();case`arrayBuffer`:return await e.arrayBuffer();case`formData`:return await e.formData();case`stream`:return e.body;default:{let t=e.headers.get(`content-type`)??``;if(t.includes(`application/json`))return await e.json();if(t.includes(`text/`))return await e.text();if(t.includes(`multipart/form-data`))return await e.formData();if(t.includes(`application/octet-stream`)||t.includes(`image/`)||t.includes(`audio/`)||t.includes(`video/`))return await e.blob();try{return await e.json()}catch{return await e.text()}}}}normalizeError(e){return e instanceof Error&&e.name===`TimeoutError`?{message:`Request timed out`,code:`TIMEOUT`}:e instanceof DOMException&&e.name===`AbortError`?{message:`Request aborted`,code:`ABORTED`}:e instanceof Error?e.name===`AbortError`||e.message.includes(`abort`)?{message:`Request aborted`,code:`ABORTED`}:{message:e.message,code:e.name===`TypeError`?`NETWORK_ERROR`:`UNKNOWN_ERROR`,originalError:e}:{message:String(e),code:`UNKNOWN_ERROR`,originalError:e}}isHttpErrorDetails(e){return typeof e==`object`&&!!e&&`message`in e&&`code`in e}getMaxAttempts(e){return e?`maxAttempts`in e?e.maxAttempts:100:1}getRetryStrategy(e){return e?`shouldRetry`in e?e:new J(e.maxAttempts,e.backoffMs):{shouldRetry:()=>!1,delayMs:()=>0}}delay(e){return new Promise(t=>setTimeout(t,e))}},$=class extends Error{status;code;response;constructor(e,t,n,r){super(e),this.name=`HttpError`,this.status=t,this.code=n,this.response=r}};function te(e){return e instanceof $}function ne(e){return new Q(e)}e.AggressiveRetry=f,e.CachePlugin=G,e.CacheStore=S,e.CircuitBreakerRetry=m,e.ConservativeRetry=p,e.DedupePlugin=K,e.Defer=I,e.Err=n,e.HttpClient=Q,e.HttpError=$,e.LoggerPlugin=U,e.MetricsPlugin=W,e.MiddlewarePipeline=O,e.Ok=t,e.PluginManager=H,e.RequestDeduplicator=w,e.TypedObservable=M,e.cacheMiddleware=ee,e.createApiUrl=F,e.createCacheMiddleware=C,e.createDedupeMiddleware=T,e.createHttpClient=ne,e.createPipeline=D,e.createProjectionTransformer=u,e.createRequiredFieldsValidator=i,e.createSchemaValidator=r,e.createStreamingMiddleware=B,e.createTypeGuard=N,e.createTypedApiClient=j,e.createTypedRequest=A,e.createTypedResponse=k,e.createUrl=P,e.createWrapperTransformer=d,e.dedupeMiddleware=E,e.handleStream=L,e.isHttpError=te,e.retry=x,e.streamToFile=z,e.streamingMiddleware=V,e.transformCamelToSnake=c,e.transformFlatten=l,e.transformSnakeToCamel=s,e.validatorIsArray=a,e.validatorIsObject=o,e.withTimeout=b});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { IHttpClient, HttpRequestConfig, HttpResponse, HttpErrorDetails, RequestInterceptor, ResponseInterceptor, HttpClientConfig, PaginateOptions, PollOptions, Disposer, Result } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Main HTTP Client Implementation
|
|
4
|
+
* Combines fetch API with axios-like convenience + modern features
|
|
5
|
+
*/
|
|
6
|
+
export declare class HttpClient implements IHttpClient {
|
|
7
|
+
private requestInterceptors;
|
|
8
|
+
private responseInterceptors;
|
|
9
|
+
private cache;
|
|
10
|
+
private config;
|
|
11
|
+
private requestQueue;
|
|
12
|
+
private pendingRequests;
|
|
13
|
+
constructor(config?: HttpClientConfig);
|
|
14
|
+
/**
|
|
15
|
+
* Core request method — all others delegate to this
|
|
16
|
+
* Pipeline: hooks → cache → interceptors → fetch → parse → validate → transform → interceptors → cache → hooks
|
|
17
|
+
*/
|
|
18
|
+
request<T = unknown>(config: HttpRequestConfig): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
19
|
+
get<T = unknown>(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
20
|
+
post<T = unknown>(url: string, body?: unknown, config?: Omit<HttpRequestConfig, 'url' | 'method' | 'body'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
21
|
+
put<T = unknown>(url: string, body?: unknown, config?: Omit<HttpRequestConfig, 'url' | 'method' | 'body'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
22
|
+
patch<T = unknown>(url: string, body?: unknown, config?: Omit<HttpRequestConfig, 'url' | 'method' | 'body'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
23
|
+
delete<T = unknown>(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
24
|
+
head(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<void>, HttpErrorDetails>>;
|
|
25
|
+
options(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<void>, HttpErrorDetails>>;
|
|
26
|
+
addRequestInterceptor(interceptor: RequestInterceptor): Disposer;
|
|
27
|
+
addResponseInterceptor(interceptor: ResponseInterceptor): Disposer;
|
|
28
|
+
clearInterceptors(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Clear all cached responses
|
|
31
|
+
*/
|
|
32
|
+
clearCache(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Cancel all pending requests
|
|
35
|
+
*/
|
|
36
|
+
cancelAll(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Number of currently active requests
|
|
39
|
+
*/
|
|
40
|
+
get activeRequests(): number;
|
|
41
|
+
/**
|
|
42
|
+
* Queue stats (only relevant when maxConcurrent > 0)
|
|
43
|
+
*/
|
|
44
|
+
get queueStats(): {
|
|
45
|
+
active: number;
|
|
46
|
+
pending: number;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Create a child client that inherits config + interceptors.
|
|
50
|
+
* Overrides are merged (headers are shallow-merged, rest overwrites).
|
|
51
|
+
*/
|
|
52
|
+
extend(overrides?: HttpClientConfig): HttpClient;
|
|
53
|
+
/**
|
|
54
|
+
* Auto-paginate a GET endpoint. Yields arrays of items per page.
|
|
55
|
+
*
|
|
56
|
+
* Usage:
|
|
57
|
+
* ```ts
|
|
58
|
+
* for await (const users of client.paginate<UserListResponse>('/users', {
|
|
59
|
+
* getItems: (data) => data.items,
|
|
60
|
+
* getNextPage: (data, cfg) =>
|
|
61
|
+
* data.nextCursor ? { ...cfg, query: { ...cfg.query, cursor: data.nextCursor } } : null,
|
|
62
|
+
* })) {
|
|
63
|
+
* console.log(users); // items from this page
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
paginate<T = unknown>(url: string, options: PaginateOptions<T>, config?: Omit<HttpRequestConfig, 'url' | 'method'>): AsyncGenerator<T[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Poll an endpoint until a condition is met.
|
|
70
|
+
* Returns the final response that satisfied `until()`.
|
|
71
|
+
*
|
|
72
|
+
* Usage:
|
|
73
|
+
* ```ts
|
|
74
|
+
* const result = await client.poll<Job>('/jobs/123', {
|
|
75
|
+
* intervalMs: 2000,
|
|
76
|
+
* maxAttempts: 30,
|
|
77
|
+
* until: (job) => job.status === 'completed',
|
|
78
|
+
* onPoll: (job, attempt) => console.log(`Attempt ${attempt}: ${job.status}`),
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
poll<T = unknown>(url: string, options: PollOptions<T>, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
83
|
+
private buildRequest;
|
|
84
|
+
private buildUrl;
|
|
85
|
+
private getCacheKey;
|
|
86
|
+
private fetchWithTimeout;
|
|
87
|
+
/**
|
|
88
|
+
* Wraps response body with a progress-tracking ReadableStream
|
|
89
|
+
*/
|
|
90
|
+
private trackDownloadProgress;
|
|
91
|
+
private parseResponse;
|
|
92
|
+
private parseBody;
|
|
93
|
+
private normalizeError;
|
|
94
|
+
private isHttpErrorDetails;
|
|
95
|
+
private getMaxAttempts;
|
|
96
|
+
private getRetryStrategy;
|
|
97
|
+
private delay;
|
|
98
|
+
}
|
|
99
|
+
export declare class HttpError extends Error {
|
|
100
|
+
status: number;
|
|
101
|
+
code: string;
|
|
102
|
+
response?: unknown;
|
|
103
|
+
constructor(message: string, status: number, code: string, response?: unknown);
|
|
104
|
+
}
|
|
105
|
+
export declare function isHttpError(error: unknown): error is HttpError;
|
|
106
|
+
/**
|
|
107
|
+
* Factory function (Dependency Inversion — easier testing)
|
|
108
|
+
*/
|
|
109
|
+
export declare function createHttpClient(config?: HttpClientConfig): HttpClient;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client Plugin - Main Exports
|
|
3
|
+
*/
|
|
4
|
+
export { createHttpClient, HttpClient, HttpError, isHttpError } from './http-client.js';
|
|
5
|
+
export type { IHttpClient, HttpRequest, HttpRequestConfig, HttpResponse, HttpErrorDetails, RequestInterceptor, ResponseInterceptor, RetryStrategy, CacheStrategy, Validator, Transformer, HttpClientConfig, ResponseType, RequestHooks, ProgressEvent, PaginateOptions, PollOptions, Disposer, Result, } from '../types/index.js';
|
|
6
|
+
export { Ok, Err } from '../types/index.js';
|
|
7
|
+
export { withTimeout, retry, createSchemaValidator, createRequiredFieldsValidator, validatorIsArray, validatorIsObject, transformSnakeToCamel, transformCamelToSnake, transformFlatten, createProjectionTransformer, createWrapperTransformer, AggressiveRetry, ConservativeRetry, CircuitBreakerRetry, CacheStore, createCacheMiddleware, cacheMiddleware, RequestDeduplicator, createDedupeMiddleware, dedupeMiddleware, MiddlewarePipeline, createPipeline, type Middleware, type HttpContext, createTypedResponse, createTypedRequest, createTypedApiClient, createTypeGuard, createUrl, createApiUrl, TypedObservable, Defer, type TypedResponse, type ApiEndpoint, type ApiSchema, type Url, type ApiUrl, type FileUrl, type UnionToIntersection, type ResultOf, handleStream, streamToFile, createStreamingMiddleware, streamingMiddleware, type StreamOptions, PluginManager, LoggerPlugin, MetricsPlugin, CachePlugin, DedupePlugin, type Plugin, } from '../utils';
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client Plugin - Type Definitions
|
|
3
|
+
* Combines fetch power + axios convenience with SOLID principles
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Represents a successful or failed result
|
|
7
|
+
* Allows for type-safe error handling without exceptions
|
|
8
|
+
*/
|
|
9
|
+
export type Result<T, E = Error> = {
|
|
10
|
+
ok: true;
|
|
11
|
+
value: T;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
error: E;
|
|
15
|
+
};
|
|
16
|
+
export declare const Ok: <T>(value: T) => Result<T>;
|
|
17
|
+
export declare const Err: <E>(error: E) => Result<never, E>;
|
|
18
|
+
export interface HttpRequest {
|
|
19
|
+
url: string;
|
|
20
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
query?: Record<string, string | number | boolean>;
|
|
24
|
+
params?: Record<string, string | number>;
|
|
25
|
+
timeout?: number;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
export interface HttpResponse<T = unknown> {
|
|
29
|
+
status: number;
|
|
30
|
+
statusText: string;
|
|
31
|
+
headers: Headers;
|
|
32
|
+
data: T;
|
|
33
|
+
request: HttpRequest;
|
|
34
|
+
duration: number;
|
|
35
|
+
}
|
|
36
|
+
export interface HttpErrorDetails {
|
|
37
|
+
message: string;
|
|
38
|
+
status?: number;
|
|
39
|
+
statusText?: string;
|
|
40
|
+
code?: string;
|
|
41
|
+
originalError?: unknown;
|
|
42
|
+
}
|
|
43
|
+
export interface ProgressEvent {
|
|
44
|
+
loaded: number;
|
|
45
|
+
total: number;
|
|
46
|
+
percent: number;
|
|
47
|
+
}
|
|
48
|
+
export interface RequestHooks<T = unknown> {
|
|
49
|
+
onStart?: (request: HttpRequest) => void;
|
|
50
|
+
onSuccess?: (response: HttpResponse<T>) => void;
|
|
51
|
+
onError?: (error: HttpErrorDetails) => void;
|
|
52
|
+
onFinally?: () => void;
|
|
53
|
+
onRetry?: (attempt: number, error: HttpErrorDetails) => void;
|
|
54
|
+
}
|
|
55
|
+
export interface RequestInterceptor {
|
|
56
|
+
onRequest(request: HttpRequest): HttpRequest | Promise<HttpRequest>;
|
|
57
|
+
onError?(error: HttpErrorDetails): HttpErrorDetails | Promise<HttpErrorDetails>;
|
|
58
|
+
}
|
|
59
|
+
export interface ResponseInterceptor {
|
|
60
|
+
onResponse<T = unknown>(response: HttpResponse<T>): HttpResponse<T> | Promise<HttpResponse<T>>;
|
|
61
|
+
onError?(error: HttpErrorDetails): HttpErrorDetails | Promise<HttpErrorDetails>;
|
|
62
|
+
}
|
|
63
|
+
export interface RetryStrategy {
|
|
64
|
+
shouldRetry(attempt: number, error: HttpErrorDetails): boolean;
|
|
65
|
+
delayMs(attempt: number): number;
|
|
66
|
+
}
|
|
67
|
+
export interface CacheStrategy {
|
|
68
|
+
get(key: string): unknown | null;
|
|
69
|
+
set(key: string, value: unknown, ttlMs?: number): void;
|
|
70
|
+
clear(): void;
|
|
71
|
+
has(key: string): boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface Validator {
|
|
74
|
+
validate(data: unknown): Result<unknown, HttpErrorDetails>;
|
|
75
|
+
}
|
|
76
|
+
export interface Transformer {
|
|
77
|
+
transform(data: unknown): unknown;
|
|
78
|
+
}
|
|
79
|
+
export type ResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData' | 'stream' | 'auto';
|
|
80
|
+
export interface HttpRequestConfig extends HttpRequest {
|
|
81
|
+
retry?: RetryStrategy | {
|
|
82
|
+
maxAttempts: number;
|
|
83
|
+
backoffMs: number;
|
|
84
|
+
};
|
|
85
|
+
timeout?: number;
|
|
86
|
+
validate?: Validator;
|
|
87
|
+
transform?: Transformer;
|
|
88
|
+
cache?: {
|
|
89
|
+
enabled: boolean;
|
|
90
|
+
ttlMs?: number;
|
|
91
|
+
};
|
|
92
|
+
responseType?: ResponseType;
|
|
93
|
+
hooks?: RequestHooks;
|
|
94
|
+
onUploadProgress?: (event: ProgressEvent) => void;
|
|
95
|
+
onDownloadProgress?: (event: ProgressEvent) => void;
|
|
96
|
+
}
|
|
97
|
+
export interface PaginateOptions<T> {
|
|
98
|
+
/** Extract items from a response page */
|
|
99
|
+
getItems: (data: T) => unknown[];
|
|
100
|
+
/** Return the config for the next page, or null to stop */
|
|
101
|
+
getNextPage: (data: T, currentConfig: Omit<HttpRequestConfig, 'url' | 'method'>) => Omit<HttpRequestConfig, 'url' | 'method'> | null;
|
|
102
|
+
}
|
|
103
|
+
export interface PollOptions<T> {
|
|
104
|
+
/** Interval between polls in ms */
|
|
105
|
+
intervalMs: number;
|
|
106
|
+
/** Max number of polls (0 = unlimited) */
|
|
107
|
+
maxAttempts?: number;
|
|
108
|
+
/** Stop polling when this returns true */
|
|
109
|
+
until: (data: T) => boolean;
|
|
110
|
+
/** Called on each successful poll */
|
|
111
|
+
onPoll?: (data: T, attempt: number) => void;
|
|
112
|
+
}
|
|
113
|
+
export interface IHttpClient {
|
|
114
|
+
request<T = unknown>(config: HttpRequestConfig): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
115
|
+
get<T = unknown>(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
116
|
+
post<T = unknown>(url: string, body?: unknown, config?: Omit<HttpRequestConfig, 'url' | 'method' | 'body'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
117
|
+
put<T = unknown>(url: string, body?: unknown, config?: Omit<HttpRequestConfig, 'url' | 'method' | 'body'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
118
|
+
patch<T = unknown>(url: string, body?: unknown, config?: Omit<HttpRequestConfig, 'url' | 'method' | 'body'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
119
|
+
delete<T = unknown>(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
120
|
+
head(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<void>, HttpErrorDetails>>;
|
|
121
|
+
options(url: string, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<void>, HttpErrorDetails>>;
|
|
122
|
+
addRequestInterceptor(interceptor: RequestInterceptor): Disposer;
|
|
123
|
+
addResponseInterceptor(interceptor: ResponseInterceptor): Disposer;
|
|
124
|
+
clearInterceptors(): void;
|
|
125
|
+
extend(config?: HttpClientConfig): IHttpClient;
|
|
126
|
+
paginate<T = unknown>(url: string, options: PaginateOptions<T>, config?: Omit<HttpRequestConfig, 'url' | 'method'>): AsyncIterable<T[]>;
|
|
127
|
+
poll<T = unknown>(url: string, options: PollOptions<T>, config?: Omit<HttpRequestConfig, 'url' | 'method'>): Promise<Result<HttpResponse<T>, HttpErrorDetails>>;
|
|
128
|
+
cancelAll(): void;
|
|
129
|
+
clearCache(): void;
|
|
130
|
+
}
|
|
131
|
+
/** Function that removes a previously added interceptor */
|
|
132
|
+
export type Disposer = () => void;
|
|
133
|
+
export interface HttpClientConfig {
|
|
134
|
+
baseURL?: string;
|
|
135
|
+
defaultHeaders?: Record<string, string>;
|
|
136
|
+
defaultTimeout?: number;
|
|
137
|
+
cacheStrategy?: CacheStrategy;
|
|
138
|
+
validateStatus?: (status: number) => boolean;
|
|
139
|
+
maxConcurrent?: number;
|
|
140
|
+
defaultResponseType?: ResponseType;
|
|
141
|
+
defaultHooks?: RequestHooks;
|
|
142
|
+
}
|