Browse Source

pub-query checks: cacheTimeout, cacheTags, autorefresh and so. TODO: mutationss

asmer 3 months ago
parent
commit
b1b85627e0
5 changed files with 132 additions and 18 deletions
  1. 35 5
      src/App.tsx
  2. 4 0
      src/lib/pub/createPub.ts
  3. 89 11
      src/lib/pub/query/index.ts
  4. 2 0
      src/lib/pub/react/hooks/usePub.ts
  5. 2 2
      src/main.tsx

+ 35 - 5
src/App.tsx

@@ -8,16 +8,46 @@ import {createQueryPub} from './lib/pub';
 
 const history = createBrowserHistory()
 
-console.log(createQueryPub)
+//console.log(createQueryPub)
+//
+
+const {queryPub, createQuery} = createQueryPub({
+    queryFunc(route, id=''){
+        return fetch(`https://swapi.dev/api/${route}/${id}`).then(res => res.json())
+    },
+    cacheTimeout: 5000,
+    //hidePendingOnRefresh: false,
+})
+
+const {usePeopleQuery} = createQuery({promiseName: 'people',
+                                     cacheTagFunc: (payload) => payload.results.map(man => ({type: 'people', id: +man.url.split('/')[5]}))})
+
+//queryPub.subscribe(() => console.log(JSON.stringify(queryPub, null, 4)))
+
 
 
 
 
 function App() {
-  return (
-    <Router history={history}>
-    </Router>
-  )
+    const queryState = usePeopleQuery('people')
+    const [,setRandomState] = useState<number>()
+
+    useEffect(() => {
+        //setInterval(() => setRandomState(Math.random()), 1000)
+    },[])
+
+    console.log(queryState)
+
+    const {status, payload,cacheTags} = queryState
+    //console.log(JSON.stringify(cacheTags))
+    return (
+        <Router history={history}>
+        {status === 'PENDING' && <h1>Loading</h1>}
+        {status === 'FULFILLED' && payload.results.map(man => <h2 key={man.url}>{man.name}</h2>)}
+
+
+        </Router>
+    )
 }
 
 export default App

+ 4 - 0
src/lib/pub/createPub.ts

@@ -23,6 +23,10 @@ const createPub = (state:object={}): Pub => {
                                                                               })
     
     state.subscribe = subscribe
+    //TODO: ___unwrap and ___assign methods
+    //___unwrap for getting state without wrapping proxy
+    //___set for silent prop assign
+    //___assign for setting bunch of properties w/o calling subscribers for everyone (only one subscribers call for many updates)
     
     const proxy = new Proxy(state, {
         get(obj:object, prop:string|symbol){

+ 89 - 11
src/lib/pub/query/index.ts

@@ -1,4 +1,5 @@
 import createPub from '../';
+import {usePub}  from '../';
 
 type QueryFuncType = (...args: Array<any>) => Promise;
 
@@ -25,6 +26,7 @@ const defaultOptions:CreateQueryPubOptions = {
     cacheTimeout: 2 * 60 * 1000,
     hidePendingOnRefresh: true,
     avoidRepeatInProgress: true,
+    forceRefreshAfterTimeout: true,
 }
 
 export default (options:CreateQueryPubOptions) => {
@@ -33,33 +35,109 @@ export default (options:CreateQueryPubOptions) => {
 
     options = {...defaultOptions, ...options}
 
-    const createQuery = (queryFunc: QueryFuncType):Function => {
-        return (promiseName:string, isMutation:boolean) => {
+    const withQueryFunc = (queryFunc?: QueryFuncType):Function => {
+        return ({promiseName, isMutation, cacheTagFunc}) => {
             const qF = queryFunc || options.queryFunc
 
-            const forceQuery = (...args) => {
+            const makeQuery = (...args:any[]) => {
                 return qF(...args)
             }
 
-            const query = (...args) => {
+            const query = (...args:any[]) => {
                 if (isMutation){
-                    if (options.avoidRepeatInProgress && queryPub.mutations?.[promiseName]?.status === 'PENDING'){
+                    if (options.avoidRepeatInProgress && queryPub.mutations[promiseName]?.refreshing){
                         return currentPromises.mutations[promiseName]
                     }
                     else {
-                        queryPub.mutations[promiseName] = {status: 'PENDING'}
-                        return currentPromises.mutations[promiseName] = forceQuery(...args).then(payload => queryPub.mutations[promiseName] = {status: 'FULFILLED', payload},
-                                                                                                 error   => queryPub.mutations[promiseName] = {status: 'REJECTED', error})
+                        if (!options.hidePendingOnRefresh || !queryPub.mutations[promiseName]){
+                            queryPub.mutations[promiseName] = {status: 'PENDING', refreshing: true}
+                        }
+                        if (!queryPub.mutations[promiseName].refreshing){
+                            queryPub.mutations[promiseName].refreshing = true
+                        }
+                        return currentPromises.mutations[promiseName] = makeQuery(...args).then(payload => queryPub.mutations[promiseName] = {status: 'FULFILLED', payload},
+                                                                                                error   => queryPub.mutations[promiseName] = {status: 'REJECTED', error})
                     }
                 }
                 if (!isMutation){
                     const stringifiedArgs = JSON.stringify(args)
-                    queryPub.queries[promiseName][stringifiedArgs]
+                    if ((!options.cacheTimeout && queryPub.queries[promiseName]?.[stringifiedArgs].status === 'FULFILLED') || 
+                        (queryPub.queries[promiseName]?.[stringifiedArgs] && (Date.now() - queryPub.queries[promiseName][stringifiedArgs].timeStamp) < options.cacheTimeout)){
+
+                        return currentPromises.queries[promiseName][stringifiedArgs] 
+                    }
+                    return forceQuery(...args)
                 }
             }
-            return {forceQuery, query}
+
+            const forceQuery = (...args:any[]) =>  {
+                if (isMutation) return query(...args)
+
+
+
+                const stringifiedArgs = JSON.stringify(args)
+                if (!queryPub.queries[promiseName]) {
+                    queryPub.queries[promiseName] = {}
+                }
+                if (!currentPromises.queries[promiseName]){
+                    currentPromises.queries[promiseName] = {}
+                }
+
+
+
+                if (options.avoidRepeatInProgress && queryPub.queries[promiseName]?.[stringifiedArgs]?.refreshing){
+                    return currentPromises.queries[promiseName][stringifiedArgs]
+                }
+
+                if (!options.hidePendingOnRefresh || !queryPub.queries[promiseName]?.[stringifiedArgs]){
+                    queryPub.queries[promiseName][stringifiedArgs] = {status: 'PENDING', timeStamp: Date.now(), refreshing: true}
+                }
+
+                if (!queryPub.queries[promiseName][stringifiedArgs].refreshing){
+                    queryPub.queries[promiseName][stringifiedArgs].refreshing = true
+                }
+
+                if (queryPub.queries[promiseName][stringifiedArgs].timeoutId){
+                    clearTimeout(queryPub.queries[promiseName][stringifiedArgs].timeoutId)
+                }
+
+
+
+                return currentPromises.queries[promiseName][stringifiedArgs] = makeQuery(...args).then(payload => queryPub.queries[promiseName][stringifiedArgs] = {    
+                                                                                                                                                                        status: 'FULFILLED', 
+                                                                                                                                                                        payload, 
+                                                                                                                                                                        timeStamp: Date.now(), 
+                                                                                                                                                                        cacheTags: cacheTagFunc(payload),
+                                                                                                                                                                        timeoutId: options.cacheTimeout ? setTimeout(() =>forceQuery(...args), options.cacheTimeout) : null
+                                                                                                                                                                    },
+                                                                                                       error   => queryPub.queries[promiseName][stringifiedArgs] = {
+                                                                                                                                                                        status: 'REJECTED', 
+                                                                                                                                                                        error, 
+                                                                                                                                                                        timeStamp: Date.now(),
+                                                                                                                                                                        timeoutId: options.cacheTimeout ? setTimeout(() =>forceQuery(...args), options.cacheTimeout) : null
+                                                                                                                                                                    })
+            }
+
+            const hookName = `use${promiseName[0].toUpperCase() + promiseName.slice(1) + (isMutation ? 'Mutation' : 'Query')}`
+
+            const useMutation = () => {
+                usePub(queryPub.mutations)
+
+                return [query, queryPub.mutations[promiseName] || {status: 'INIT'}]
+            }
+
+            const useQuery = (...args) => {
+                query(...args)
+
+                usePub(queryPub.queries[promiseName])
+
+                const stringifiedArgs = JSON.stringify(args)
+                return queryPub.queries[promiseName][stringifiedArgs]
+            }
+
+            return {makeQuery, query, forceQuery, [hookName]: isMutation ? useMutation : useQuery}
         }
     }
 
-    return {queryPub, createQuery}
+    return {queryPub, withQueryFunc, ...(options.queryFunc ? {createQuery: withQueryFunc(options.queryFunc)}: {})}
 }

+ 2 - 0
src/lib/pub/react/hooks/usePub.ts

@@ -4,6 +4,8 @@ import { Pub } from '../../';
 export default (pub:Pub):Pub => {
     const [, setRandomState] = useState<number>()
     useEffect(() => {
+        if (!pub) return
+
         const unsubscribe = pub.subscribe(() => setRandomState(Math.random()))
         return () => {
             unsubscribe()

+ 2 - 2
src/main.tsx

@@ -4,7 +4,7 @@ import App from './App.tsx'
 import './index.css'
 
 ReactDOM.createRoot(document.getElementById('root')!).render(
-  <React.StrictMode>
+  //<React.StrictMode>
     <App />
-  </React.StrictMode>,
+  //</React.StrictMode>,
 )