arthurfiorette / axios-cache-interceptor

📬 Small and efficient cache interceptor for axios. Etag, Cache-Control, TTL, HTTP headers and more!
https://axios-cache-interceptor.js.org
MIT License
703 stars 58 forks source link

Question: How to do a proper offline-mode #780

Closed she11sh0cked closed 10 months ago

she11sh0cked commented 10 months ago

What happened?

I've got a question on how to properly use axios-cache-interceptor in an offline context.

Basically I'm working on an application that isn't guaranteed to have access to the API at all times, but I want my tools to be transparent to that.

Currently my issue is, that the cache seems to be invalidated before the request was made successfully, which leaves me with no data.

# request called
/data/test/people/query { method: 'POST', data: {}, params: URLSearchParams {} }
# custom fs storage called
find: .cache/-1181719077.json
remove: .cache/-1181719077.json
set: .cache/-1181719077.json {
  state: 'loading',
  previous: 'empty',
  data: undefined,
  createdAt: undefined
}
find: .cache/-1181719077.json
remove: .cache/-1181719077.json
# request catch called
Error: No response received

What I've done to address this is setting the ttl to the integer maximum.

ttl: Number.MAX_SAFE_INTEGER

So i guess my question would be if there is a better way to get proper offline support or if this is out-of-scope.

axios-cache-interceptor version

v1.5.1

Node / Browser Version

Node v20.11.0

Axios Version

v1.6.5

What storage is being used

Another one

Relevant debugging log output


> axios-cache-interceptor-repro@1.0.0 start
> ts-node index.ts

You are using a development build. Make sure to use the correct build in production
https://axios-cache-interceptor.js.org/guide/getting-started

### ONLINE REQUESTS ###
{ id: '405330793', msg: 'Updated stale request' }
{
  id: '405330793',
  msg: 'Sending request, waiting for response',
  data: { overrideCache: false, state: 'stale' }
}
{
  id: '405330793',
  msg: 'Useful response configuration found',
  data: {
    cacheConfig: {
      update: {},
      ttl: 1,
      methods: [Array],
      cachePredicate: [Object],
      etag: true,
      modifiedSince: false,
      interpretHeader: false,
      cacheTakeover: true,
      staleIfError: true,
      override: false,
      hydrate: undefined
    },
    cacheResponse: {
      data: 'good',
      status: 200,
      statusText: 'OK',
      headers: [Object [AxiosHeaders]]
    }
  }
}
{ id: '405330793', msg: 'Found waiting deferred(s) and resolved them' }
{
  id: '405330793',
  msg: 'Response cached',
  data: {
    cache: {
      state: 'cached',
      ttl: 1,
      staleTtl: undefined,
      createdAt: 1705969287415,
      data: [Object]
    },
    response: {
      status: 200,
      statusText: 'OK',
      headers: [Object [AxiosHeaders]],
      config: [Object],
      request: [ClientRequest],
      data: 'good',
      id: '405330793',
      cached: false
    }
  }
}
good NOT CACHED
{
  id: '-1446184403',
  msg: 'Sending request, waiting for response',
  data: { overrideCache: false, state: 'empty' }
}
{
  id: '-1446184403',
  msg: 'Useful response configuration found',
  data: {
    cacheConfig: {
      update: {},
      ttl: 1,
      methods: [Array],
      cachePredicate: [Object],
      etag: true,
      modifiedSince: false,
      interpretHeader: false,
      cacheTakeover: true,
      staleIfError: true,
      override: false,
      hydrate: undefined
    },
    cacheResponse: {
      data: 'bad',
      status: 200,
      statusText: 'OK',
      headers: [Object [AxiosHeaders]]
    }
  }
}
{
  id: '-1446184403',
  msg: 'Found waiting deferred(s) and resolved them'
}
{
  id: '-1446184403',
  msg: 'Response cached',
  data: {
    cache: {
      state: 'cached',
      ttl: 1,
      staleTtl: undefined,
      createdAt: 1705969287419,
      data: [Object]
    },
    response: {
      status: 200,
      statusText: 'OK',
      headers: [Object [AxiosHeaders]],
      config: [Object],
      request: [ClientRequest],
      data: 'bad',
      id: '-1446184403',
      cached: false
    }
  }
}
bad NOT CACHED

### OFFLINE REQUESTS ###
{ id: '405330793', msg: 'Updated stale request' }
{
  id: '405330793',
  msg: 'Sending request, waiting for response',
  data: { overrideCache: false, state: 'stale' }
}
{
  id: '405330793',
  msg: 'Found cache if stale config for rejected response',
  data: {
    error: AxiosError [AggregateError]
        at Function.AxiosError.from (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/axios/lib/core/AxiosError.js:89:14)
        at RedirectableRequest.handleRequestError (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/axios/lib/adapters/http.js:610:25)
        at RedirectableRequest.emit (node:events:518:28)
        at RedirectableRequest.emit (node:domain:488:12)
        at ClientRequest.eventHandlers.<computed> (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/follow-redirects/index.js:38:24)
        at ClientRequest.emit (node:events:518:28)
        at ClientRequest.emit (node:domain:488:12)
        at Socket.socketErrorListener (node:_http_client:495:9)
        at Socket.emit (node:events:518:28)
        at Socket.emit (node:domain:488:12) {
      code: 'ECONNREFUSED',
      errors: [Array],
      config: [Object],
      request: [Writable],
      cause: [AggregateError]
    },
    config: {
      transitional: [Object],
      adapter: [Array],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function (anonymous)],
      headers: [Object [AxiosHeaders]],
      baseURL: 'http://localhost:1337',
      cache: [Object],
      url: '/good',
      method: 'get',
      id: '405330793',
      data: undefined
    },
    staleIfError: true
  }
}
{
  id: '405330793',
  msg: 'staleIfError resolved this response with cached data',
  data: {
    error: AxiosError [AggregateError]
        at Function.AxiosError.from (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/axios/lib/core/AxiosError.js:89:14)
        at RedirectableRequest.handleRequestError (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/axios/lib/adapters/http.js:610:25)
        at RedirectableRequest.emit (node:events:518:28)
        at RedirectableRequest.emit (node:domain:488:12)
        at ClientRequest.eventHandlers.<computed> (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/follow-redirects/index.js:38:24)
        at ClientRequest.emit (node:events:518:28)
        at ClientRequest.emit (node:domain:488:12)
        at Socket.socketErrorListener (node:_http_client:495:9)
        at Socket.emit (node:events:518:28)
        at Socket.emit (node:domain:488:12) {
      code: 'ECONNREFUSED',
      errors: [Array],
      config: [Object],
      request: [Writable],
      cause: [AggregateError]
    },
    config: {
      transitional: [Object],
      adapter: [Array],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function (anonymous)],
      headers: [Object [AxiosHeaders]],
      baseURL: 'http://localhost:1337',
      cache: [Object],
      url: '/good',
      method: 'get',
      id: '405330793',
      data: undefined
    },
    cache: {
      state: 'loading',
      previous: 'stale',
      data: [Object],
      createdAt: 1705969287415
    }
  }
}
good CACHED
{
  id: '-1446184403',
  msg: 'Sending request, waiting for response',
  data: { overrideCache: false, state: 'empty' }
}
{
  id: '-1446184403',
  msg: 'Caught an error in the request interceptor',
  data: {
    cache: { state: 'loading', previous: 'empty' },
    error: AxiosError [AggregateError]
        at Function.AxiosError.from (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/axios/lib/core/AxiosError.js:89:14)
        at RedirectableRequest.handleRequestError (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/axios/lib/adapters/http.js:610:25)
        at RedirectableRequest.emit (node:events:518:28)
        at RedirectableRequest.emit (node:domain:488:12)
        at ClientRequest.eventHandlers.<computed> (/home/user/src/.research/axios-cache-interceptor-repro/node_modules/follow-redirects/index.js:38:24)
        at ClientRequest.emit (node:events:518:28)
        at ClientRequest.emit (node:domain:488:12)
        at Socket.socketErrorListener (node:_http_client:495:9)
        at Socket.emit (node:events:518:28)
        at Socket.emit (node:domain:488:12) {
      code: 'ECONNREFUSED',
      errors: [Array],
      config: [Object],
      request: [Writable],
      cause: [AggregateError]
    },
    config: {
      transitional: [Object],
      adapter: [Array],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function (anonymous)],
      headers: [Object [AxiosHeaders]],
      baseURL: 'http://localhost:1337',
      cache: [Object],
      url: '/bad',
      method: 'get',
      id: '-1446184403',
      data: undefined
    }
  }
}
/home/user/src/.research/axios-cache-interceptor-repro/helper/error.ts:9
    throw new Error("No response received");
          ^
Error: No response received
    at handleRequestError (/home/user/src/.research/axios-cache-interceptor-repro/helper/error.ts:9:11)
    at async /home/user/src/.research/axios-cache-interceptor-repro/index.ts:44:25
arthurfiorette commented 10 months ago

Please provide the debugging logs as required in this issue template. Also a reproducible example is preferred.

she11sh0cked commented 10 months ago

Thanks! Iv'e attached the debugging logs and created a reproducible example. While I created the repro I think I found the issue. The cache works as expected when I remove the cache-control: "private, no-cache, no-store, must-revalidate", as described in the README and code