Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
name: Publish to npm
name: Release

# Keep the file name: npm's trusted publisher for fetchff is bound to publish.yml
on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: read
id-token: write # Required for OIDC
permissions: {}

concurrency:
group: release
cancel-in-progress: false

jobs:
publish:
release:
name: Release
runs-on: ubuntu-latest
environment:
name: npm
url: https://www.npmjs.com/package/fetchff
permissions:
contents: read
id-token: write # Required for OIDC
steps:
# github.triggering_actor is the user who started this run, or re-ran it
- name: Check that an admin started the release
env:
GH_TOKEN: ${{ github.token }}
ACTOR: ${{ github.triggering_actor }}
run: |
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$ACTOR/permission" --jq .permission)
if [ "$permission" != "admin" ]; then
echo "::error::Only admins can publish to npm, and $ACTOR has $permission access."
exit 1
fi

- uses: actions/checkout@v7

- uses: actions/setup-node@v7
Expand All @@ -21,9 +43,23 @@ jobs:
registry-url: "https://registry.npmjs.org"
package-manager-cache: false # Never publish a build made from cached dependencies

- name: Check that the release tag matches the version
if: github.event_name == 'release'
env:
TAG: ${{ github.event.release.tag_name }}
run: |
version=$(node -p "require('./package.json').version")
if [ "${TAG#v}" != "$version" ]; then
echo "::error::The release tag $TAG does not match version $version in package.json."
exit 1
fi

- name: Install dependencies
run: npm ci

- name: Test
run: npm test -- --ci --maxWorkers=2

- name: Build
run: npm run build

Expand Down
112 changes: 62 additions & 50 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/browser/index.global.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/browser/index.global.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/browser/index.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/browser/index.mjs.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/node/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/node/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/react/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/react/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/react/index.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import {useMemo,useRef,useCallback,useSyncExternalStore}from'react';import {generateCacheKey,buildConfig,getCache,getInFlightPromise,fetchf,getCachedResponse,subscribe,addTimeout,abortRequest,createAbortError,deleteCache}from'fetchff';var F=-1,y=2e3,m=new Map,S=e=>{e&&m.set(e,(m.get(e)||0)+1);},q=(e,t,r,o)=>{if(!e)return;let a=f(e);if(!a)return;let i=a-1;i<=0?(m.delete(e),t===F&&addTimeout("r:"+e,()=>{abortRequest(e,createAbortError("Request to "+o+" aborted","AbortError")),f(e)||deleteCache(e,true);},r!=null?r:y)):m.set(e,i);},f=e=>e&&m.get(e)||0;var Z=300,B=Object.freeze({data:null,error:null,isFetching:false,mutate:()=>Promise.resolve(null),config:{},headers:{}}),K=Object.freeze({...B,isFetching:true}),$=[null,{},null],k=new Set(["GET","HEAD","get","head"]);function ne(e,t={}){var b,U,_;let r=useMemo(()=>e===null?null:generateCacheKey(buildConfig(e,t)),[t.cacheKey,e,t.url,t.method,t.headers,t.body,t.params,t.urlPathParams,t.apiUrl,t.baseURL,t.withCredentials,t.credentials]),o=(b=t.dedupeTime)!=null?b:y,a=t.cacheTime||F,i=(U=t.staleTime)!=null?U:Z,R=(_=t.immediate)!=null?_:k.has(t.method||"GET"),P=useRef($);P.current=[e,t,r];let g=useCallback(()=>{let s=getCache(r);if(t.strategy==="reject"&&r&&(!s||!s.data.data&&!s.data.error)){let h=getInFlightPromise(r,o);if(h)throw h;if(!s){let[u,c,l]=P.current;if(u)throw fetchf(u,{...c,cacheKey:l,dedupeTime:o,cacheTime:a,staleTime:i,strategy:"softFail",cacheErrors:true,_isAutoKey:!c.cacheKey})}}return s?s.data.isFetching&&!t.keepPreviousData?K:s.data:R?K:B},[r]),L=useCallback(s=>{S(r),R&&e&&r&&f(r)===1&&(getCachedResponse(r,a,t)||C(false));let u=subscribe(r,s);return ()=>{q(r,a,o,e),u();}},[r,R,e,o,a]),n=useSyncExternalStore(L,g,g),C=useCallback(async(s=true,h={})=>{let[u,c,l]=P.current;if(!u)return Promise.resolve(null);let E=!!s;if(!E&&l){let A=getCachedResponse(l,a,c);if(A)return Promise.resolve(A)}let Q=E?()=>true:c.cacheBuster;return fetchf(u,{...c,cacheKey:l,...h,dedupeTime:o,cacheTime:a,staleTime:i,cacheBuster:Q,strategy:"softFail",cacheErrors:true,_isAutoKey:!c.cacheKey})},[a,o]),T=n.data,p=!T&&!n.error,d=!!e&&(n.isFetching||p&&R),x=d&&p,w=d&&!p;return {data:T,error:n.error,config:n.config,headers:n.headers,isFirstFetch:x,isFetching:d,isLoading:d,isRefetching:w,isError:n.isError,isSuccess:n.isSuccess,mutate:n.mutate,refetch:C}}export{ne as useFetcher};//# sourceMappingURL=index.mjs.map
import {useMemo,useRef,useCallback,useSyncExternalStore}from'react';import {generateCacheKey,buildConfig,getCache,getInFlightPromise,fetchf,getCachedResponse,subscribe,addTimeout,abortRequest,createAbortError,deleteCache}from'fetchff';var F=-1,y=2e3,m=new Map,I=t=>{t&&m.set(t,(m.get(t)||0)+1);},M=(t,e,s,o)=>{if(!t)return;let a=f(t);if(!a)return;let i=a-1;i<=0?(m.delete(t),e===F&&addTimeout("r:"+t,()=>{abortRequest(t,createAbortError("Request to "+o+" aborted","AbortError")),f(t)||deleteCache(t,true);},s!=null?s:y)):m.set(t,i);},f=t=>t&&m.get(t)||0;var $=300,C=Object.freeze({data:null,error:null,isFetching:false,mutate:async()=>null,config:{},headers:{}}),w=Object.freeze({...C,isFetching:true}),k=[null,{},null],ee=new Set(["GET","HEAD","get","head"]);function oe(t,e={}){var _,q,A;let s=useMemo(()=>t===null?null:generateCacheKey(buildConfig(t,e)),[e.cacheKey,t,e.url,e.method,e.headers,e.body,e.params,e.urlPathParams,e.apiUrl,e.baseURL,e.withCredentials,e.credentials]),o=(_=e.dedupeTime)!=null?_:y,a=e.cacheTime||F,i=(q=e.staleTime)!=null?q:$,R=(A=e.immediate)!=null?A:ee.has(e.method||"GET"),P=useRef(k);P.current=[t,e,s];let b=useMemo(()=>e.initialData===void 0?null:Object.freeze({...C,data:e.initialData}),[s]),T=useCallback(()=>{let r=getCache(s);if(!r&&b)return b;if(e.strategy==="reject"&&s&&(!r||!r.data.data&&!r.data.error)){let h=getInFlightPromise(s,o);if(h)throw h;if(!r){let[u,c,l]=P.current;if(u)throw fetchf(u,{...c,cacheKey:l,dedupeTime:o,cacheTime:a,staleTime:i,strategy:"softFail",cacheErrors:true,_isAutoKey:!c.cacheKey})}}return r?r.data.isFetching&&!e.keepPreviousData?w:r.data:R?w:C},[s]),x=useCallback(r=>{I(s),R&&t&&s&&f(s)===1&&(getCachedResponse(s,a,e)||g(false));let u=subscribe(s,r);return ()=>{M(s,a,o,t),u();}},[s,R,t,o,a]),n=useSyncExternalStore(x,T,T),g=useCallback(async(r=true,h={})=>{let[u,c,l]=P.current;if(!u)return null;let E=!!r;if(!E&&l){let S=getCachedResponse(l,a,c);if(S)return S}let N=E?()=>true:c.cacheBuster;return fetchf(u,{...c,cacheKey:l,...h,dedupeTime:o,cacheTime:a,staleTime:i,cacheBuster:N,strategy:"softFail",cacheErrors:true,_isAutoKey:!c.cacheKey})},[a,o]),U=n.data,p=!U&&!n.error,d=!!t&&(n.isFetching||p&&R),Q=d&&p,H=d&&!p;return {data:U,error:n.error,config:n.config,headers:n.headers,isFirstFetch:Q,isFetching:d,isLoading:d,isRefetching:H,isError:n.isError,isSuccess:n.isSuccess,mutate:n.mutate,refetch:g}}export{oe as useFetcher};//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map
2 changes: 1 addition & 1 deletion dist/react/index.mjs.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fetchff",
"version": "4.2.0",
"version": "4.3.0",
"license": "MIT",
"author": "Matt Czapliński <deindesign.pl@gmail.com>",
"repository": {
Expand Down
15 changes: 7 additions & 8 deletions src/response-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,13 @@ export const prepareResponse = <

// If it's a custom fetcher, and it does not return any Response instance, it may have its own internal handler
if (isObject(response)) {
Object.assign(response, {
error,
headers,
isFetching: false,
mutate: mutatator,
isSuccess: response.ok && !error,
isError: !!error,
});
// Assigned one by one, as Object.assign() with an object literal is measurably slower here
response.error = error;
response.headers = headers;
response.isFetching = false;
response.mutate = mutatator;
response.isSuccess = response.ok && !error;
response.isError = !!error;
}

return response;
Expand Down
Loading