@cloudflare/kv-asset-handler
Advanced tools
Comparing version 0.1.3 to 0.2.0
@@ -30,4 +30,8 @@ import { Options, CacheControl, MethodNotAllowedError, NotFoundError, InternalError } from './types'; | ||
* */ | ||
declare const getAssetFromKV: (event: FetchEvent, options?: Partial<Options>) => Promise<Response>; | ||
declare type Evt = { | ||
request: Request; | ||
waitUntil: (promise: Promise<any>) => void; | ||
}; | ||
declare const getAssetFromKV: (event: Evt, options?: Partial<Options>) => Promise<Response>; | ||
export { getAssetFromKV, mapRequestToAsset, serveSinglePageApp }; | ||
export { Options, CacheControl, MethodNotAllowedError, NotFoundError, InternalError }; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -28,6 +19,7 @@ exports.InternalError = exports.NotFoundError = exports.MethodNotAllowedError = exports.serveSinglePageApp = exports.mapRequestToAsset = exports.getAssetFromKV = void 0; | ||
? parseStringAsObject(__STATIC_CONTENT_MANIFEST) | ||
: undefined, | ||
: {}, | ||
cacheControl: defaultCacheControl, | ||
defaultMimeType: 'text/plain', | ||
defaultDocument: 'index.html', | ||
pathIsEncoded: false, | ||
}; | ||
@@ -88,14 +80,3 @@ function assignOptions(options) { | ||
exports.serveSinglePageApp = serveSinglePageApp; | ||
/** | ||
* takes the path of the incoming request, gathers the appropriate content from KV, and returns | ||
* the response | ||
* | ||
* @param {FetchEvent} event the fetch event of the triggered request | ||
* @param {{mapRequestToAsset: (string: Request) => Request, cacheControl: {bypassCache:boolean, edgeTTL: number, browserTTL:number}, ASSET_NAMESPACE: any, ASSET_MANIFEST:any}} [options] configurable options | ||
* @param {CacheControl} [options.cacheControl] determine how to cache on Cloudflare and the browser | ||
* @param {typeof(options.mapRequestToAsset)} [options.mapRequestToAsset] maps the path of incoming request to the request pathKey to look up | ||
* @param {Object | string} [options.ASSET_NAMESPACE] the binding to the namespace that script references | ||
* @param {any} [options.ASSET_MANIFEST] the map of the key to cache and store in KV | ||
* */ | ||
const getAssetFromKV = (event, options) => __awaiter(void 0, void 0, void 0, function* () { | ||
const getAssetFromKV = async (event, options) => { | ||
options = assignOptions(options); | ||
@@ -109,3 +90,3 @@ const request = event.request; | ||
const rawPathKey = new URL(request.url).pathname.replace(/^\/+/, ''); // strip any preceding /'s | ||
let pathIsEncoded = false; | ||
let pathIsEncoded = options.pathIsEncoded; | ||
let requestKey; | ||
@@ -211,3 +192,3 @@ // if options.mapRequestToAsset is explicitly passed in, always use it and assume user has own intentions | ||
if (shouldEdgeCache) { | ||
response = yield cache.match(cacheKey); | ||
response = await cache.match(cacheKey); | ||
} | ||
@@ -217,7 +198,7 @@ if (response) { | ||
if (response.body && 'cancel' in Object.getPrototypeOf(response.body)) { | ||
// Body exists and environment supports readable streams | ||
response.body.cancel(); | ||
console.log('Body exists and environment supports readable streams. Body cancelled'); | ||
} | ||
else { | ||
console.log('Environment doesnt support readable streams'); | ||
// Environment doesnt support readable streams, or null repsonse body. Nothing to do | ||
} | ||
@@ -250,3 +231,3 @@ response = new Response(null, response); | ||
else { | ||
const body = yield ASSET_NAMESPACE.get(pathKey, 'arrayBuffer'); | ||
const body = await ASSET_NAMESPACE.get(pathKey, 'arrayBuffer'); | ||
if (body === null) { | ||
@@ -291,3 +272,3 @@ throw new types_1.NotFoundError(`could not find ${pathKey} in your content namespace`); | ||
return response; | ||
}); | ||
}; | ||
exports.getAssetFromKV = getAssetFromKV; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -16,5 +7,5 @@ exports.sleep = exports.mockGlobalScope = exports.mockRequestScope = exports.mockCaches = exports.mockManifest = exports.mockKV = exports.getEvent = void 0; | ||
const getEvent = (request) => { | ||
const waitUntil = (callback) => __awaiter(void 0, void 0, void 0, function* () { | ||
yield callback; | ||
}); | ||
const waitUntil = async (callback) => { | ||
await callback; | ||
}; | ||
return { | ||
@@ -76,64 +67,60 @@ request, | ||
default: { | ||
match(key) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let cacheKey = { | ||
url: key.url, | ||
headers: {}, | ||
}; | ||
let response; | ||
if (key.headers.has('if-none-match')) { | ||
let makeStrongEtag = key.headers.get('if-none-match').replace('W/', ''); | ||
Reflect.set(cacheKey.headers, 'etag', makeStrongEtag); | ||
response = cacheStore.get(JSON.stringify(cacheKey)); | ||
async match(key) { | ||
let cacheKey = { | ||
url: key.url, | ||
headers: {}, | ||
}; | ||
let response; | ||
if (key.headers.has('if-none-match')) { | ||
let makeStrongEtag = key.headers.get('if-none-match').replace('W/', ''); | ||
Reflect.set(cacheKey.headers, 'etag', makeStrongEtag); | ||
response = cacheStore.get(JSON.stringify(cacheKey)); | ||
} | ||
else { | ||
// if client doesn't send if-none-match, we need to iterate through these keys | ||
// and just test the URL | ||
const activeCacheKeys = Array.from(cacheStore.keys()); | ||
for (const cacheStoreKey of activeCacheKeys) { | ||
if (JSON.parse(cacheStoreKey).url === key.url) { | ||
response = cacheStore.get(cacheStoreKey); | ||
} | ||
} | ||
} | ||
// TODO: write test to accomodate for rare scenarios with where range requests accomodate etags | ||
if (response && !key.headers.has('if-none-match')) { | ||
// this appears overly verbose, but is necessary to document edge cache behavior | ||
// The Range request header triggers the response header Content-Range ... | ||
const range = key.headers.get('range'); | ||
if (range) { | ||
response.headers.set('content-range', `bytes ${range.split('=').pop()}/${response.headers.get('content-length')}`); | ||
} | ||
// ... which we are using in this repository to set status 206 | ||
if (response.headers.has('content-range')) { | ||
response.status = 206; | ||
} | ||
else { | ||
// if client doesn't send if-none-match, we need to iterate through these keys | ||
// and just test the URL | ||
const activeCacheKeys = Array.from(cacheStore.keys()); | ||
for (const cacheStoreKey of activeCacheKeys) { | ||
if (JSON.parse(cacheStoreKey).url === key.url) { | ||
response = cacheStore.get(cacheStoreKey); | ||
} | ||
} | ||
response.status = 200; | ||
} | ||
// TODO: write test to accomodate for rare scenarios with where range requests accomodate etags | ||
if (response && !key.headers.has('if-none-match')) { | ||
// this appears overly verbose, but is necessary to document edge cache behavior | ||
// The Range request header triggers the response header Content-Range ... | ||
const range = key.headers.get('range'); | ||
if (range) { | ||
response.headers.set('content-range', `bytes ${range.split('=').pop()}/${response.headers.get('content-length')}`); | ||
} | ||
// ... which we are using in this repository to set status 206 | ||
if (response.headers.has('content-range')) { | ||
response.status = 206; | ||
} | ||
else { | ||
response.status = 200; | ||
} | ||
let etag = response.headers.get('etag'); | ||
if (etag && !etag.includes('W/')) { | ||
response.headers.set('etag', `W/${etag}`); | ||
} | ||
let etag = response.headers.get('etag'); | ||
if (etag && !etag.includes('W/')) { | ||
response.headers.set('etag', `W/${etag}`); | ||
} | ||
return response; | ||
}); | ||
} | ||
return response; | ||
}, | ||
put(key, val) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let headers = new Headers(val.headers); | ||
let url = new URL(key.url); | ||
let resWithBody = new Response(val.body, { headers, status: 200 }); | ||
let resNoBody = new Response(null, { headers, status: 304 }); | ||
let cacheKey = { | ||
url: key.url, | ||
headers: { | ||
etag: `"${url.pathname.replace('/', '')}"`, | ||
}, | ||
}; | ||
cacheStore.set(JSON.stringify(cacheKey), resNoBody); | ||
cacheKey.headers = {}; | ||
cacheStore.set(JSON.stringify(cacheKey), resWithBody); | ||
return; | ||
}); | ||
async put(key, val) { | ||
let headers = new Headers(val.headers); | ||
let url = new URL(key.url); | ||
let resWithBody = new Response(val.body, { headers, status: 200 }); | ||
let resNoBody = new Response(null, { headers, status: 304 }); | ||
let cacheKey = { | ||
url: key.url, | ||
headers: { | ||
etag: `"${url.pathname.replace('/', '')}"`, | ||
}, | ||
}; | ||
cacheStore.set(JSON.stringify(cacheKey), resNoBody); | ||
cacheKey.headers = {}; | ||
cacheStore.set(JSON.stringify(cacheKey), resWithBody); | ||
return; | ||
}, | ||
@@ -147,5 +134,5 @@ }, | ||
Object.assign(global, makeServiceWorkerEnv()); | ||
Object.assign(global, { __STATIC_CONTENT_MANIFEST: exports.mockManifest() }); | ||
Object.assign(global, { __STATIC_CONTENT: exports.mockKV(store) }); | ||
Object.assign(global, { caches: exports.mockCaches() }); | ||
Object.assign(global, { __STATIC_CONTENT_MANIFEST: (0, exports.mockManifest)() }); | ||
Object.assign(global, { __STATIC_CONTENT: (0, exports.mockKV)(store) }); | ||
Object.assign(global, { caches: (0, exports.mockCaches)() }); | ||
} | ||
@@ -155,4 +142,4 @@ exports.mockRequestScope = mockRequestScope; | ||
function mockGlobalScope() { | ||
Object.assign(global, { __STATIC_CONTENT_MANIFEST: exports.mockManifest() }); | ||
Object.assign(global, { __STATIC_CONTENT: exports.mockKV(store) }); | ||
Object.assign(global, { __STATIC_CONTENT_MANIFEST: (0, exports.mockManifest)() }); | ||
Object.assign(global, { __STATIC_CONTENT: (0, exports.mockKV)(store) }); | ||
} | ||
@@ -159,0 +146,0 @@ exports.mockGlobalScope = mockGlobalScope; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const ava_1 = require("ava"); | ||
const mocks_1 = require("../mocks"); | ||
mocks_1.mockGlobalScope(); | ||
(0, mocks_1.mockGlobalScope)(); | ||
const index_1 = require("../index"); | ||
ava_1.default('getAssetFromKV return correct val from KV and default caching', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/key1.txt')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
(0, ava_1.default)('getAssetFromKV return correct val from KV and default caching', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/key1.txt')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(res.headers.get('cache-control'), null); | ||
t.is(res.headers.get('cf-cache-status'), 'MISS'); | ||
t.is(yield res.text(), 'val1'); | ||
t.is(await res.text(), 'val1'); | ||
t.true(res.headers.get('content-type').includes('text')); | ||
@@ -29,23 +20,23 @@ } | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV evaluated the file matching the extensionless path first /client/ -> client', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request(`https://foo.com/client/`)); | ||
const res = yield index_1.getAssetFromKV(event); | ||
t.is(yield res.text(), 'important file'); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV evaluated the file matching the extensionless path first /client/ -> client', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request(`https://foo.com/client/`)); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
t.is(await res.text(), 'important file'); | ||
t.true(res.headers.get('content-type').includes('text')); | ||
})); | ||
ava_1.default('getAssetFromKV evaluated the file matching the extensionless path first /client -> client', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request(`https://foo.com/client`)); | ||
const res = yield index_1.getAssetFromKV(event); | ||
t.is(yield res.text(), 'important file'); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV evaluated the file matching the extensionless path first /client -> client', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request(`https://foo.com/client`)); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
t.is(await res.text(), 'important file'); | ||
t.true(res.headers.get('content-type').includes('text')); | ||
})); | ||
ava_1.default('getAssetFromKV if not in asset manifest still returns nohash.txt', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/nohash.txt')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV if not in asset manifest still returns nohash.txt', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/nohash.txt')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'no hash but still got some result'); | ||
t.is(await res.text(), 'no hash but still got some result'); | ||
t.true(res.headers.get('content-type').includes('text')); | ||
@@ -56,15 +47,15 @@ } | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV if no asset manifest /client -> client fails', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request(`https://foo.com/client`)); | ||
const error = yield t.throwsAsync(index_1.getAssetFromKV(event, { ASSET_MANIFEST: {} })); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV if no asset manifest /client -> client fails', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request(`https://foo.com/client`)); | ||
const error = await t.throwsAsync((0, index_1.getAssetFromKV)(event, { ASSET_MANIFEST: {} })); | ||
t.is(error.status, 404); | ||
})); | ||
ava_1.default('getAssetFromKV if sub/ -> sub/index.html served', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request(`https://foo.com/sub`)); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV if sub/ -> sub/index.html served', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request(`https://foo.com/sub`)); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'picturedis'); | ||
t.is(await res.text(), 'picturedis'); | ||
} | ||
@@ -74,9 +65,9 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV gets index.html by default for / requests', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV gets index.html by default for / requests', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'index.html'); | ||
t.is(await res.text(), 'index.html'); | ||
t.true(res.headers.get('content-type').includes('html')); | ||
@@ -87,9 +78,9 @@ } | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV non ASCII path support', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/测试.html')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV non ASCII path support', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/测试.html')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'My filename is non-ascii'); | ||
t.is(await res.text(), 'My filename is non-ascii'); | ||
} | ||
@@ -99,9 +90,9 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV supports browser percent encoded URLs', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://example.com/%not-really-percent-encoded.html')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV supports browser percent encoded URLs', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://example.com/%not-really-percent-encoded.html')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'browser percent encoded'); | ||
t.is(await res.text(), 'browser percent encoded'); | ||
} | ||
@@ -111,9 +102,9 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV supports user percent encoded URLs', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/%2F.html')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV supports user percent encoded URLs', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/%2F.html')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'user percent encoded'); | ||
t.is(await res.text(), 'user percent encoded'); | ||
} | ||
@@ -123,12 +114,12 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV only decode URL when necessary', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event1 = mocks_1.getEvent(new Request('https://blah.com/%E4%BD%A0%E5%A5%BD.html')); | ||
const event2 = mocks_1.getEvent(new Request('https://blah.com/你好.html')); | ||
const res1 = yield index_1.getAssetFromKV(event1); | ||
const res2 = yield index_1.getAssetFromKV(event2); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV only decode URL when necessary', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event1 = (0, mocks_1.getEvent)(new Request('https://blah.com/%E4%BD%A0%E5%A5%BD.html')); | ||
const event2 = (0, mocks_1.getEvent)(new Request('https://blah.com/你好.html')); | ||
const res1 = await (0, index_1.getAssetFromKV)(event1); | ||
const res2 = await (0, index_1.getAssetFromKV)(event2); | ||
if (res1 && res2) { | ||
t.is(yield res1.text(), 'Im important'); | ||
t.is(yield res2.text(), 'Im important'); | ||
t.is(await res1.text(), 'Im important'); | ||
t.is(await res2.text(), 'Im important'); | ||
} | ||
@@ -138,12 +129,12 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV Support for user decode url path', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event1 = mocks_1.getEvent(new Request('https://blah.com/%E4%BD%A0%E5%A5%BD/')); | ||
const event2 = mocks_1.getEvent(new Request('https://blah.com/你好/')); | ||
const res1 = yield index_1.getAssetFromKV(event1); | ||
const res2 = yield index_1.getAssetFromKV(event2); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV Support for user decode url path', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event1 = (0, mocks_1.getEvent)(new Request('https://blah.com/%E4%BD%A0%E5%A5%BD/')); | ||
const event2 = (0, mocks_1.getEvent)(new Request('https://blah.com/你好/')); | ||
const res1 = await (0, index_1.getAssetFromKV)(event1); | ||
const res2 = await (0, index_1.getAssetFromKV)(event2); | ||
if (res1 && res2) { | ||
t.is(yield res1.text(), 'My path is non-ascii'); | ||
t.is(yield res2.text(), 'My path is non-ascii'); | ||
t.is(await res1.text(), 'My path is non-ascii'); | ||
t.is(await res2.text(), 'My path is non-ascii'); | ||
} | ||
@@ -153,8 +144,8 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV custom key modifier', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/docs/sub/blah.png')); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV custom key modifier', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/docs/sub/blah.png')); | ||
const customRequestMapper = (request) => { | ||
let defaultModifiedRequest = index_1.mapRequestToAsset(request); | ||
let defaultModifiedRequest = (0, index_1.mapRequestToAsset)(request); | ||
let url = new URL(defaultModifiedRequest.url); | ||
@@ -164,5 +155,5 @@ url.pathname = url.pathname.replace('/docs', ''); | ||
}; | ||
const res = yield index_1.getAssetFromKV(event, { mapRequestToAsset: customRequestMapper }); | ||
const res = await (0, index_1.getAssetFromKV)(event, { mapRequestToAsset: customRequestMapper }); | ||
if (res) { | ||
t.is(yield res.text(), 'picturedis'); | ||
t.is(await res.text(), 'picturedis'); | ||
} | ||
@@ -172,9 +163,9 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV request override with existing manifest file', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
(0, ava_1.default)('getAssetFromKV request override with existing manifest file', async (t) => { | ||
// see https://github.com/cloudflare/kv-asset-handler/pull/159 for more info | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/image.png')); // real file in manifest | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/image.png')); // real file in manifest | ||
const customRequestMapper = (request) => { | ||
let defaultModifiedRequest = index_1.mapRequestToAsset(request); | ||
let defaultModifiedRequest = (0, index_1.mapRequestToAsset)(request); | ||
let url = new URL(defaultModifiedRequest.url); | ||
@@ -184,5 +175,5 @@ url.pathname = '/image.webp'; // other different file in manifest | ||
}; | ||
const res = yield index_1.getAssetFromKV(event, { mapRequestToAsset: customRequestMapper }); | ||
const res = await (0, index_1.getAssetFromKV)(event, { mapRequestToAsset: customRequestMapper }); | ||
if (res) { | ||
t.is(yield res.text(), 'imagewebp'); | ||
t.is(await res.text(), 'imagewebp'); | ||
} | ||
@@ -192,7 +183,7 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV when setting browser caching', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/')); | ||
const res = yield index_1.getAssetFromKV(event, { cacheControl: { browserTTL: 22 } }); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV when setting browser caching', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/')); | ||
const res = await (0, index_1.getAssetFromKV)(event, { cacheControl: { browserTTL: 22 } }); | ||
if (res) { | ||
@@ -204,7 +195,7 @@ t.is(res.headers.get('cache-control'), 'max-age=22'); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV when setting custom cache setting', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event1 = mocks_1.getEvent(new Request('https://blah.com/')); | ||
const event2 = mocks_1.getEvent(new Request('https://blah.com/key1.png?blah=34')); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV when setting custom cache setting', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event1 = (0, mocks_1.getEvent)(new Request('https://blah.com/')); | ||
const event2 = (0, mocks_1.getEvent)(new Request('https://blah.com/key1.png?blah=34')); | ||
const cacheOnlyPngs = (req) => { | ||
@@ -221,4 +212,4 @@ if (new URL(req.url).pathname.endsWith('.png')) | ||
}; | ||
const res1 = yield index_1.getAssetFromKV(event1, { cacheControl: cacheOnlyPngs }); | ||
const res2 = yield index_1.getAssetFromKV(event2, { cacheControl: cacheOnlyPngs }); | ||
const res1 = await (0, index_1.getAssetFromKV)(event1, { cacheControl: cacheOnlyPngs }); | ||
const res2 = await (0, index_1.getAssetFromKV)(event2, { cacheControl: cacheOnlyPngs }); | ||
if (res1 && res2) { | ||
@@ -233,9 +224,9 @@ t.is(res1.headers.get('cache-control'), null); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV caches on two sequential requests', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV caches on two sequential requests', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const resourceKey = 'cache.html'; | ||
const resourceVersion = JSON.parse(mocks_1.mockManifest())[resourceKey]; | ||
const event1 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`, { | ||
const resourceVersion = JSON.parse((0, mocks_1.mockManifest)())[resourceKey]; | ||
const event1 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`, { | ||
headers: { | ||
@@ -245,5 +236,5 @@ 'if-none-match': `"${resourceVersion}"`, | ||
})); | ||
const res1 = yield index_1.getAssetFromKV(event1, { cacheControl: { edgeTTL: 720, browserTTL: 720 } }); | ||
yield mocks_1.sleep(1); | ||
const res2 = yield index_1.getAssetFromKV(event2); | ||
const res1 = await (0, index_1.getAssetFromKV)(event1, { cacheControl: { edgeTTL: 720, browserTTL: 720 } }); | ||
await (0, mocks_1.sleep)(1); | ||
const res2 = await (0, index_1.getAssetFromKV)(event2); | ||
if (res1 && res2) { | ||
@@ -257,9 +248,9 @@ t.is(res1.headers.get('cf-cache-status'), 'MISS'); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV does not store max-age on two sequential requests', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV does not store max-age on two sequential requests', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const resourceKey = 'cache.html'; | ||
const resourceVersion = JSON.parse(mocks_1.mockManifest())[resourceKey]; | ||
const event1 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`, { | ||
const resourceVersion = JSON.parse((0, mocks_1.mockManifest)())[resourceKey]; | ||
const event1 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`, { | ||
headers: { | ||
@@ -269,5 +260,5 @@ 'if-none-match': `"${resourceVersion}"`, | ||
})); | ||
const res1 = yield index_1.getAssetFromKV(event1, { cacheControl: { edgeTTL: 720 } }); | ||
yield mocks_1.sleep(100); | ||
const res2 = yield index_1.getAssetFromKV(event2); | ||
const res1 = await (0, index_1.getAssetFromKV)(event1, { cacheControl: { edgeTTL: 720 } }); | ||
await (0, mocks_1.sleep)(100); | ||
const res2 = await (0, index_1.getAssetFromKV)(event2); | ||
if (res1 && res2) { | ||
@@ -282,7 +273,7 @@ t.is(res1.headers.get('cf-cache-status'), 'MISS'); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV does not cache on Cloudflare when bypass cache set', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/')); | ||
const res = yield index_1.getAssetFromKV(event, { cacheControl: { bypassCache: true } }); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV does not cache on Cloudflare when bypass cache set', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/')); | ||
const res = await (0, index_1.getAssetFromKV)(event, { cacheControl: { bypassCache: true } }); | ||
if (res) { | ||
@@ -295,9 +286,9 @@ t.is(res.headers.get('cache-control'), null); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV with no trailing slash on root', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV with no trailing slash on root', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'index.html'); | ||
t.is(await res.text(), 'index.html'); | ||
} | ||
@@ -307,9 +298,9 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV with no trailing slash on a subdirectory', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/sub/blah.png')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV with no trailing slash on a subdirectory', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/sub/blah.png')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'picturedis'); | ||
t.is(await res.text(), 'picturedis'); | ||
} | ||
@@ -319,15 +310,15 @@ else { | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV no result throws an error', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/random')); | ||
const error = yield t.throwsAsync(index_1.getAssetFromKV(event)); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV no result throws an error', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/random')); | ||
const error = await t.throwsAsync((0, index_1.getAssetFromKV)(event)); | ||
t.is(error.status, 404); | ||
})); | ||
ava_1.default('getAssetFromKV TTls set to null should not cache on browser or edge', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/')); | ||
const res1 = yield index_1.getAssetFromKV(event, { cacheControl: { browserTTL: null, edgeTTL: null } }); | ||
yield mocks_1.sleep(100); | ||
const res2 = yield index_1.getAssetFromKV(event, { cacheControl: { browserTTL: null, edgeTTL: null } }); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV TTls set to null should not cache on browser or edge', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/')); | ||
const res1 = await (0, index_1.getAssetFromKV)(event, { cacheControl: { browserTTL: null, edgeTTL: null } }); | ||
await (0, mocks_1.sleep)(100); | ||
const res2 = await (0, index_1.getAssetFromKV)(event, { cacheControl: { browserTTL: null, edgeTTL: null } }); | ||
if (res1 && res2) { | ||
@@ -342,13 +333,13 @@ t.is(res1.headers.get('cf-cache-status'), null); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV passing in a custom NAMESPACE serves correct asset', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
let CUSTOM_NAMESPACE = mocks_1.mockKV({ | ||
}); | ||
(0, ava_1.default)('getAssetFromKV passing in a custom NAMESPACE serves correct asset', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
let CUSTOM_NAMESPACE = (0, mocks_1.mockKV)({ | ||
'key1.123HASHBROWN.txt': 'val1', | ||
}); | ||
Object.assign(global, { CUSTOM_NAMESPACE }); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/')); | ||
const res = yield index_1.getAssetFromKV(event); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/')); | ||
const res = await (0, index_1.getAssetFromKV)(event); | ||
if (res) { | ||
t.is(yield res.text(), 'index.html'); | ||
t.is(await res.text(), 'index.html'); | ||
t.true(res.headers.get('content-type').includes('html')); | ||
@@ -359,26 +350,26 @@ } | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV when custom namespace without the asset should fail', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
let CUSTOM_NAMESPACE = mocks_1.mockKV({ | ||
}); | ||
(0, ava_1.default)('getAssetFromKV when custom namespace without the asset should fail', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
let CUSTOM_NAMESPACE = (0, mocks_1.mockKV)({ | ||
'key5.123HASHBROWN.txt': 'customvalu', | ||
}); | ||
const event = mocks_1.getEvent(new Request('https://blah.com')); | ||
const error = yield t.throwsAsync(index_1.getAssetFromKV(event, { ASSET_NAMESPACE: CUSTOM_NAMESPACE })); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com')); | ||
const error = await t.throwsAsync((0, index_1.getAssetFromKV)(event, { ASSET_NAMESPACE: CUSTOM_NAMESPACE })); | ||
t.is(error.status, 404); | ||
})); | ||
ava_1.default('getAssetFromKV when namespace not bound fails', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV when namespace not bound fails', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
var MY_CUSTOM_NAMESPACE = undefined; | ||
Object.assign(global, { MY_CUSTOM_NAMESPACE }); | ||
const event = mocks_1.getEvent(new Request('https://blah.com/')); | ||
const error = yield t.throwsAsync(index_1.getAssetFromKV(event, { ASSET_NAMESPACE: MY_CUSTOM_NAMESPACE })); | ||
const event = (0, mocks_1.getEvent)(new Request('https://blah.com/')); | ||
const error = await t.throwsAsync((0, index_1.getAssetFromKV)(event, { ASSET_NAMESPACE: MY_CUSTOM_NAMESPACE })); | ||
t.is(error.status, 500); | ||
})); | ||
ava_1.default('getAssetFromKV when if-none-match === active resource version, should revalidate', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV when if-none-match === active resource version, should revalidate', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const resourceKey = 'key1.png'; | ||
const resourceVersion = JSON.parse(mocks_1.mockManifest())[resourceKey]; | ||
const event1 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`, { | ||
const resourceVersion = JSON.parse((0, mocks_1.mockManifest)())[resourceKey]; | ||
const event1 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`, { | ||
headers: { | ||
@@ -388,5 +379,5 @@ 'if-none-match': `W/"${resourceVersion}"`, | ||
})); | ||
const res1 = yield index_1.getAssetFromKV(event1, { cacheControl: { edgeTTL: 720 } }); | ||
yield mocks_1.sleep(100); | ||
const res2 = yield index_1.getAssetFromKV(event2); | ||
const res1 = await (0, index_1.getAssetFromKV)(event1, { cacheControl: { edgeTTL: 720 } }); | ||
await (0, mocks_1.sleep)(100); | ||
const res2 = await (0, index_1.getAssetFromKV)(event2); | ||
if (res1 && res2) { | ||
@@ -399,7 +390,7 @@ t.is(res1.headers.get('cf-cache-status'), 'MISS'); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV when if-none-match equals etag of stale resource then should bypass cache', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV when if-none-match equals etag of stale resource then should bypass cache', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const resourceKey = 'key1.png'; | ||
const resourceVersion = JSON.parse(mocks_1.mockManifest())[resourceKey]; | ||
const resourceVersion = JSON.parse((0, mocks_1.mockManifest)())[resourceKey]; | ||
const req1 = new Request(`https://blah.com/${resourceKey}`, { | ||
@@ -415,7 +406,7 @@ headers: { | ||
}); | ||
const event = mocks_1.getEvent(req1); | ||
const event2 = mocks_1.getEvent(req2); | ||
const res1 = yield index_1.getAssetFromKV(event, { cacheControl: { edgeTTL: 720 } }); | ||
const res2 = yield index_1.getAssetFromKV(event); | ||
const res3 = yield index_1.getAssetFromKV(event2); | ||
const event = (0, mocks_1.getEvent)(req1); | ||
const event2 = (0, mocks_1.getEvent)(req2); | ||
const res1 = await (0, index_1.getAssetFromKV)(event, { cacheControl: { edgeTTL: 720 } }); | ||
const res2 = await (0, index_1.getAssetFromKV)(event); | ||
const res3 = await (0, index_1.getAssetFromKV)(event2); | ||
if (res1 && res2 && res3) { | ||
@@ -431,7 +422,7 @@ t.is(res1.headers.get('cf-cache-status'), 'MISS'); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV when resource in cache, etag should be weakened before returned to eyeball', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('getAssetFromKV when resource in cache, etag should be weakened before returned to eyeball', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
const resourceKey = 'key1.png'; | ||
const resourceVersion = JSON.parse(mocks_1.mockManifest())[resourceKey]; | ||
const resourceVersion = JSON.parse((0, mocks_1.mockManifest)())[resourceKey]; | ||
const req1 = new Request(`https://blah.com/${resourceKey}`, { | ||
@@ -442,5 +433,5 @@ headers: { | ||
}); | ||
const event = mocks_1.getEvent(req1); | ||
const res1 = yield index_1.getAssetFromKV(event, { cacheControl: { edgeTTL: 720 } }); | ||
const res2 = yield index_1.getAssetFromKV(event); | ||
const event = (0, mocks_1.getEvent)(req1); | ||
const res1 = await (0, index_1.getAssetFromKV)(event, { cacheControl: { edgeTTL: 720 } }); | ||
const res2 = await (0, index_1.getAssetFromKV)(event); | ||
if (res1 && res2) { | ||
@@ -453,9 +444,9 @@ t.is(res1.headers.get('cf-cache-status'), 'MISS'); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV if-none-match not sent but resource in cache, should return cache hit 200 OK', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
(0, ava_1.default)('getAssetFromKV if-none-match not sent but resource in cache, should return cache hit 200 OK', async (t) => { | ||
const resourceKey = 'cache.html'; | ||
const event = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`)); | ||
const res1 = yield index_1.getAssetFromKV(event, { cacheControl: { edgeTTL: 720 } }); | ||
yield mocks_1.sleep(1); | ||
const res2 = yield index_1.getAssetFromKV(event); | ||
const event = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`)); | ||
const res1 = await (0, index_1.getAssetFromKV)(event, { cacheControl: { edgeTTL: 720 } }); | ||
await (0, mocks_1.sleep)(1); | ||
const res2 = await (0, index_1.getAssetFromKV)(event); | ||
if (res1 && res2) { | ||
@@ -470,11 +461,11 @@ t.is(res1.headers.get('cf-cache-status'), 'MISS'); | ||
} | ||
})); | ||
ava_1.default('getAssetFromKV if range request submitted and resource in cache, request fulfilled', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
(0, ava_1.default)('getAssetFromKV if range request submitted and resource in cache, request fulfilled', async (t) => { | ||
const resourceKey = 'cache.html'; | ||
const event1 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = mocks_1.getEvent(new Request(`https://blah.com/${resourceKey}`, { headers: { range: 'bytes=0-10' } })); | ||
const res1 = index_1.getAssetFromKV(event1, { cacheControl: { edgeTTL: 720 } }); | ||
yield res1; | ||
yield mocks_1.sleep(2); | ||
const res2 = yield index_1.getAssetFromKV(event2); | ||
const event1 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`)); | ||
const event2 = (0, mocks_1.getEvent)(new Request(`https://blah.com/${resourceKey}`, { headers: { range: 'bytes=0-10' } })); | ||
const res1 = (0, index_1.getAssetFromKV)(event1, { cacheControl: { edgeTTL: 720 } }); | ||
await res1; | ||
await (0, mocks_1.sleep)(2); | ||
const res2 = await (0, index_1.getAssetFromKV)(event2); | ||
if (res2.headers.has('content-range')) { | ||
@@ -486,3 +477,3 @@ t.is(res2.status, 206); | ||
} | ||
})); | ||
}); | ||
ava_1.default.todo('getAssetFromKV when body not empty, should invoke .cancel()'); |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const ava_1 = require("ava"); | ||
const mocks_1 = require("../mocks"); | ||
mocks_1.mockGlobalScope(); | ||
(0, mocks_1.mockGlobalScope)(); | ||
const index_1 = require("../index"); | ||
ava_1.default('mapRequestToAsset() correctly changes /about -> /about/index.html', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
(0, ava_1.default)('mapRequestToAsset() correctly changes /about -> /about/index.html', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
let path = '/about'; | ||
let request = new Request(`https://foo.com${path}`); | ||
let newRequest = index_1.mapRequestToAsset(request); | ||
let newRequest = (0, index_1.mapRequestToAsset)(request); | ||
t.is(newRequest.url, request.url + '/index.html'); | ||
})); | ||
ava_1.default('mapRequestToAsset() correctly changes /about/ -> /about/index.html', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('mapRequestToAsset() correctly changes /about/ -> /about/index.html', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
let path = '/about/'; | ||
let request = new Request(`https://foo.com${path}`); | ||
let newRequest = index_1.mapRequestToAsset(request); | ||
let newRequest = (0, index_1.mapRequestToAsset)(request); | ||
t.is(newRequest.url, request.url + 'index.html'); | ||
})); | ||
ava_1.default('mapRequestToAsset() correctly changes /about.me/ -> /about.me/index.html', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('mapRequestToAsset() correctly changes /about.me/ -> /about.me/index.html', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
let path = '/about.me/'; | ||
let request = new Request(`https://foo.com${path}`); | ||
let newRequest = index_1.mapRequestToAsset(request); | ||
let newRequest = (0, index_1.mapRequestToAsset)(request); | ||
t.is(newRequest.url, request.url + 'index.html'); | ||
})); | ||
ava_1.default('mapRequestToAsset() correctly changes /about -> /about/default.html', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
mocks_1.mockRequestScope(); | ||
}); | ||
(0, ava_1.default)('mapRequestToAsset() correctly changes /about -> /about/default.html', async (t) => { | ||
(0, mocks_1.mockRequestScope)(); | ||
let path = '/about'; | ||
let request = new Request(`https://foo.com${path}`); | ||
let newRequest = index_1.mapRequestToAsset(request, { defaultDocument: 'default.html' }); | ||
let newRequest = (0, index_1.mapRequestToAsset)(request, { defaultDocument: 'default.html' }); | ||
t.is(newRequest.url, request.url + '/default.html'); | ||
})); | ||
}); |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const ava_1 = require("ava"); | ||
const mocks_1 = require("../mocks"); | ||
mocks_1.mockGlobalScope(); | ||
(0, mocks_1.mockGlobalScope)(); | ||
const index_1 = require("../index"); | ||
function testRequest(path) { | ||
mocks_1.mockRequestScope(); | ||
(0, mocks_1.mockRequestScope)(); | ||
let url = new URL('https://example.com'); | ||
@@ -23,22 +14,22 @@ url.pathname = path; | ||
} | ||
ava_1.default('serveSinglePageApp returns root asset path when request path ends in .html', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
(0, ava_1.default)('serveSinglePageApp returns root asset path when request path ends in .html', async (t) => { | ||
let path = '/foo/thing.html'; | ||
let request = testRequest(path); | ||
let expected_request = testRequest('/index.html'); | ||
let actual_request = index_1.serveSinglePageApp(request); | ||
let actual_request = (0, index_1.serveSinglePageApp)(request); | ||
t.deepEqual(expected_request, actual_request); | ||
})); | ||
ava_1.default('serveSinglePageApp returns root asset path when request path does not have extension', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
(0, ava_1.default)('serveSinglePageApp returns root asset path when request path does not have extension', async (t) => { | ||
let path = '/foo/thing'; | ||
let request = testRequest(path); | ||
let expected_request = testRequest('/index.html'); | ||
let actual_request = index_1.serveSinglePageApp(request); | ||
let actual_request = (0, index_1.serveSinglePageApp)(request); | ||
t.deepEqual(expected_request, actual_request); | ||
})); | ||
ava_1.default('serveSinglePageApp returns requested asset when request path has non-html extension', (t) => __awaiter(void 0, void 0, void 0, function* () { | ||
}); | ||
(0, ava_1.default)('serveSinglePageApp returns requested asset when request path has non-html extension', async (t) => { | ||
let path = '/foo/thing.js'; | ||
let request = testRequest(path); | ||
let expected_request = request; | ||
let actual_request = index_1.serveSinglePageApp(request); | ||
let actual_request = (0, index_1.serveSinglePageApp)(request); | ||
t.deepEqual(expected_request, actual_request); | ||
})); | ||
}); |
@@ -14,2 +14,3 @@ export declare type CacheControl = { | ||
defaultDocument: string; | ||
pathIsEncoded: boolean; | ||
}; | ||
@@ -16,0 +17,0 @@ export declare class KVError extends Error { |
{ | ||
"name": "@cloudflare/kv-asset-handler", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "Routes requests to KV assets", | ||
@@ -40,14 +40,14 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"mime": "^2.5.2" | ||
"mime": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"@ava/typescript": "^1.1.1", | ||
"@cloudflare/workers-types": "^2.2.2", | ||
"@ava/typescript": "^3.0.0", | ||
"@cloudflare/workers-types": "^3.0.0", | ||
"@types/mime": "^2.0.3", | ||
"@types/node": "^15.3.0", | ||
"@types/node": "^15.14.9", | ||
"ava": "^3.15.0", | ||
"prettier": "^2.3.0", | ||
"prettier": "^2.4.1", | ||
"service-worker-mock": "^2.0.5", | ||
"typescript": "^4.2.4" | ||
"typescript": "^4.4.4" | ||
} | ||
} |
110
README.md
@@ -26,11 +26,11 @@ # @cloudflare/kv-asset-handler | ||
- [`cacheControl`](#-cachecontrol) | ||
- [`browserTTL`](#-browserttl) | ||
- [`edgeTTL`](#-edgettl) | ||
- [`bypassCache`](#-bypasscache) | ||
- [`ASSET_NAMESPACE`](#-asset-namespace) | ||
- [`ASSET_MANIFEST` (optional)](#-asset-manifest---optional) | ||
- [`browserTTL`](#browserttl) | ||
- [`edgeTTL`](#edgettl) | ||
- [`bypassCache`](#bypasscache) | ||
- [`ASSET_NAMESPACE` (required for ES Modules)](#asset_namespace-required-for-es-modules) | ||
- [`ASSET_MANIFEST` (required for ES Modules)](#asset_manifest-required-for-es-modules) | ||
* [Helper functions](#helper-functions) | ||
- [`mapRequestToAsset`](#-maprequesttoasset-1) | ||
- [`serveSinglePageApp`](#-servesinglepageapp) | ||
- [`mapRequestToAsset`](#maprequesttoasset-1) | ||
- [`serveSinglePageApp`](#servesinglepageapp) | ||
* [Cache revalidation and etags](#cache-revalidation-and-etags) | ||
@@ -53,5 +53,5 @@ | ||
getAssetFromKV(FetchEvent) => Promise<Response> | ||
getAssetFromKV(Evt) => Promise<Response> | ||
`getAssetFromKV` is an async function that takes a `FetchEvent` object and returns a `Response` object if the request matches an asset in KV, otherwise it will throw a `KVError`. | ||
`getAssetFromKV` is an async function that takes an `Evt` object (containing a `Request` and a [`waitUntil`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#waituntil)) and returns a `Response` object if the request matches an asset in KV, otherwise it will throw a `KVError`. | ||
@@ -72,5 +72,44 @@ #### Example | ||
#### ES Modules | ||
```js | ||
import { getAssetFromKV, NotFoundError, MethodNotAllowedError } from '@cloudflare/kv-asset-handler' | ||
import manifestJSON from '__STATIC_CONTENT_MANIFEST' | ||
const assetManifest = JSON.parse(manifestJSON) | ||
export default { | ||
async fetch(request, env, ctx) { | ||
if (request.url.includes('/docs')) { | ||
try { | ||
return await getAssetFromKV( | ||
{ | ||
request, | ||
waitUntil(promise) { | ||
return ctx.waitUntil(promise) | ||
}, | ||
}, | ||
{ | ||
ASSET_NAMESPACE: env.__STATIC_CONTENT, | ||
ASSET_MANIFEST: assetManifest, | ||
}, | ||
) | ||
} catch (e) { | ||
if (e instanceof NotFoundError) { | ||
// ... | ||
} else if (e instanceof MethodNotAllowedError) { | ||
// ... | ||
} else { | ||
return new Response('An unexpected error occurred', { status: 500 }) | ||
} | ||
} | ||
} else return fetch(request) | ||
}, | ||
} | ||
``` | ||
#### Service Worker | ||
```js | ||
import { getAssetFromKV, NotFoundError, MethodNotAllowedError } from '@cloudflare/kv-asset-handler' | ||
addEventListener('fetch', (event) => { | ||
@@ -165,3 +204,3 @@ event.respondWith(handleEvent(event)) | ||
#### `ASSET_NAMESPACE` | ||
#### `ASSET_NAMESPACE` (required for ES Modules) | ||
@@ -174,9 +213,29 @@ type: KV Namespace Binding | ||
In ES Modules format, this argument is required, and can be gotten from `env`. | ||
##### ES Module | ||
```js | ||
return getAssetFromKV( | ||
{ | ||
request, | ||
waitUntil(promise) { | ||
return ctx.waitUntil(promise) | ||
}, | ||
}, | ||
{ | ||
ASSET_NAMESPACE: env.__STATIC_CONTENT, | ||
}, | ||
) | ||
``` | ||
##### Service Worker | ||
``` | ||
return getAssetFromKV(event, { ASSET_NAMESPACE: MY_NAMESPACE }) | ||
``` | ||
#### `ASSET_MANIFEST` (optional) | ||
#### `ASSET_MANIFEST` (required for ES Modules) | ||
type: text blob (JSON formatted) | ||
type: text blob (JSON formatted) or object | ||
@@ -187,5 +246,30 @@ The mapping of requested file path to the key stored on Cloudflare. | ||
In ES Modules format, this argument is required, and can be imported. | ||
##### ES Module | ||
```js | ||
import manifestJSON from '__STATIC_CONTENT_MANIFEST' | ||
let manifest = JSON.parse(manifestJSON) | ||
manifest['index.html'] = 'index.special.html' | ||
return getAssetFromKV( | ||
{ | ||
request, | ||
waitUntil(promise) { | ||
return ctx.waitUntil(promise) | ||
}, | ||
}, | ||
{ | ||
ASSET_MANIFEST: manifest, | ||
// ... | ||
}, | ||
) | ||
``` | ||
##### Service Worker | ||
``` | ||
let assetManifest = { "index.html": "index.special.html" } | ||
return getAssetFromKV(event, { ASSET_MANIFEST: JSON.stringify(assetManifest) }) | ||
return getAssetFromKV(event, { ASSET_MANIFEST: assetManifest }) | ||
``` | ||
@@ -192,0 +276,0 @@ |
@@ -29,6 +29,7 @@ import * as mime from 'mime' | ||
? parseStringAsObject<AssetManifestType>(__STATIC_CONTENT_MANIFEST) | ||
: undefined, | ||
: {}, | ||
cacheControl: defaultCacheControl, | ||
defaultMimeType: 'text/plain', | ||
defaultDocument: 'index.html', | ||
pathIsEncoded: false, | ||
} | ||
@@ -106,3 +107,9 @@ | ||
* */ | ||
const getAssetFromKV = async (event: FetchEvent, options?: Partial<Options>): Promise<Response> => { | ||
type Evt = { | ||
request: Request | ||
waitUntil: (promise: Promise<any>) => void | ||
} | ||
const getAssetFromKV = async (event: Evt, options?: Partial<Options>): Promise<Response> => { | ||
options = assignOptions(options) | ||
@@ -119,3 +126,3 @@ | ||
const rawPathKey = new URL(request.url).pathname.replace(/^\/+/, '') // strip any preceding /'s | ||
let pathIsEncoded = false | ||
let pathIsEncoded = options.pathIsEncoded | ||
let requestKey | ||
@@ -236,6 +243,6 @@ // if options.mapRequestToAsset is explicitly passed in, always use it and assume user has own intentions | ||
if (response.body && 'cancel' in Object.getPrototypeOf(response.body)) { | ||
// Body exists and environment supports readable streams | ||
response.body.cancel() | ||
console.log('Body exists and environment supports readable streams. Body cancelled') | ||
} else { | ||
console.log('Environment doesnt support readable streams') | ||
// Environment doesnt support readable streams, or null repsonse body. Nothing to do | ||
} | ||
@@ -242,0 +249,0 @@ response = new Response(null, response) |
@@ -16,2 +16,3 @@ export type CacheControl = { | ||
defaultDocument: string | ||
pathIsEncoded: boolean | ||
} | ||
@@ -18,0 +19,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
25
330
110086
2034
+ Addedmime@3.0.0(transitive)
- Removedmime@2.6.0(transitive)
Updatedmime@^3.0.0