Skip to content

Commit

Permalink
feat(strategy): new strategies + cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ShafSpecs committed Aug 29, 2023
1 parent 6cc5cc5 commit acf8f9e
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 2 deletions.
60 changes: 60 additions & 0 deletions packages/strategy/src/cacheFirst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { RemixCache } from '@remix-pwa/cache';
import { Storage, createCache } from '@remix-pwa/cache';

import type { StrategyOptions, StrategyResponse } from './types.js';
import { isHttpRequest } from './utils.js';

export interface CacheFirstStrategyOptions extends StrategyOptions {
/**
* A callback that will be called when the network request fails before
* an attempt is made to retrieve from the cache. This is useful for stuffs like
* logging errors and queueing requests.
*
* Defaults to `undefined`
*/
fetchDidFail?: (() => void | (() => Promise<void>))[] | undefined;
}

export const cacheFirst = async ({
cache: cacheName,
cacheOptions,
fetchDidFail = undefined,
}: CacheFirstStrategyOptions): Promise<StrategyResponse> => {
return async (request: Request | URL) => {
if (!isHttpRequest(request)) {
return new Response('Not a HTTP request', { status: 403 });
}

let remixCache: RemixCache;

if (typeof cacheName === 'string') {
Storage.init();
remixCache = Storage.has(cacheName)
? (Storage.get(cacheName) as RemixCache)
: createCache({ name: cacheName, ...cacheOptions });
} else {
Storage.init();
remixCache = cacheName;
}

const response = await remixCache.match(request);

if (!response) {
try {
const networkResponse = await fetch(request);

remixCache.put(request, networkResponse.clone());

return networkResponse;
} catch (err) {
if (fetchDidFail) {
await Promise.all(fetchDidFail.map(cb => cb()));
}

throw err;
}
}

return response;
};
};
5 changes: 5 additions & 0 deletions packages/strategy/src/cacheOnly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { RemixCache } from '@remix-pwa/cache';
import { Storage, createCache } from '@remix-pwa/cache';

import type { StrategyOptions, StrategyResponse } from './types.js';
import { isHttpRequest } from './utils.js';

export interface CacheOnlyStrategyOptions extends StrategyOptions {
/**
Expand All @@ -27,6 +28,10 @@ export const cacheOnly = async ({
cacheOptions,
}: CacheOnlyStrategyOptions): Promise<StrategyResponse> => {
return async (request: Request | URL) => {
if (!isHttpRequest(request)) {
return new Response('Not a HTTP request', { status: 403 });
}

let remixCache: RemixCache;

if (typeof cacheName === 'string') {
Expand Down
11 changes: 9 additions & 2 deletions packages/strategy/src/networkFirst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { RemixCache } from '@remix-pwa/cache';
import { Storage, createCache } from '@remix-pwa/cache';

import type { StrategyOptions, StrategyResponse } from './types.js';
import { isHttpRequest } from './utils.js';

export interface NetworkFirstStrategyOptions extends StrategyOptions {
/**
Expand Down Expand Up @@ -35,6 +36,10 @@ export const networkFirst = async ({
networkTimeoutSeconds = 10,
}: NetworkFirstStrategyOptions): Promise<StrategyResponse> => {
return async (request: Request | URL) => {
if (!isHttpRequest(request)) {
return new Response('Not a HTTP request', { status: 403 });
}

let remixCache: RemixCache;

if (typeof cacheName === 'string') {
Expand Down Expand Up @@ -82,9 +87,11 @@ export const networkFirst = async ({
return cachedResponse;
}

throw error;
return new Response(JSON.stringify({ message: 'Network Error' }), {
status: 500,
});
}

throw new Error('Failed to fetch');
throw new Error('Failed to fetch. Network timed out.');
};
};
5 changes: 5 additions & 0 deletions packages/strategy/src/networkOnly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { StrategyOptions, StrategyResponse } from './types.js';
import { isHttpRequest } from './utils.js';

export interface NetworkOnlyStrategyOptions extends Omit<StrategyOptions, 'cacheOptions' | 'cache'> {
/**
Expand Down Expand Up @@ -30,6 +31,10 @@ export const networkOnly = async ({
networkTimeoutSeconds = 10,
}: NetworkOnlyStrategyOptions): Promise<StrategyResponse> => {
return async (request: Request | URL) => {
if (!isHttpRequest(request)) {
return new Response('Not a HTTP request', { status: 403 });
}

try {
// Much tamer version of the timeout functionality
// const timeoutPromise = networkTimeoutSeconds !== Infinity ? timeout(networkTimeoutSeconds * 1000) : null;
Expand Down
64 changes: 64 additions & 0 deletions packages/strategy/src/staleWhileRevalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { RemixCache } from '@remix-pwa/cache';
import { Storage, createCache } from '@remix-pwa/cache';

import type { StrategyOptions, StrategyResponse } from './types.js';
import { isHttpRequest } from './utils.js';

export interface StaleWhileRevalidateStrategyOptions extends StrategyOptions {
/**
* A callback that will be called when the network request fails before
* an attempt is made to retrieve from the cache. This is useful for stuffs like
* logging errors and queueing requests.
*
* Defaults to `undefined`
*/
fetchDidFail?: (() => void | (() => Promise<void>))[] | undefined;
}

export const staleWhileRevalidate = async ({
cache: cacheName,
cacheOptions,
fetchDidFail = undefined,
}: StaleWhileRevalidateStrategyOptions): Promise<StrategyResponse> => {
return async (request: Request | URL) => {
if (!isHttpRequest(request)) {
return new Response('Not a HTTP request', { status: 403 });
}

let remixCache: RemixCache;

if (typeof cacheName === 'string') {
Storage.init();
remixCache = Storage.has(cacheName)
? (Storage.get(cacheName) as RemixCache)
: createCache({ name: cacheName, ...cacheOptions });
} else {
Storage.init();
remixCache = cacheName;
}

return remixCache.match(request).then(async response => {
const fetchPromise = fetch(request)
.then(async networkResponse => {
await remixCache.put(request, networkResponse.clone());

return networkResponse;
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.catch(async _err => {
if (fetchDidFail) {
await Promise.all(fetchDidFail.map(cb => cb()));
}

return new Response(JSON.stringify({ error: 'Network request failed' }), {
status: 500,
statusText: 'Network request failed',
});
});

return response || fetchPromise;
});
};
};

export const swr = staleWhileRevalidate;

0 comments on commit acf8f9e

Please sign in to comment.