From 4e87195739f2a5d9a05451b48773c8afdc680765 Mon Sep 17 00:00:00 2001 From: akiyamn Date: Sun, 24 Sep 2023 23:22:21 +1000 Subject: Initial commit (by create-cloudflare CLI) --- .../@cloudflare/kv-asset-handler/dist/index.d.ts | 36 ++ .../@cloudflare/kv-asset-handler/dist/index.js | 267 ++++++++++++ .../@cloudflare/kv-asset-handler/dist/mocks.d.ts | 14 + .../@cloudflare/kv-asset-handler/dist/mocks.js | 146 +++++++ .../dist/test/getAssetFromKV-optional.d.ts | 1 + .../dist/test/getAssetFromKV-optional.js | 22 + .../kv-asset-handler/dist/test/getAssetFromKV.d.ts | 1 + .../kv-asset-handler/dist/test/getAssetFromKV.js | 446 +++++++++++++++++++++ .../dist/test/mapRequestToAsset.d.ts | 1 + .../dist/test/mapRequestToAsset.js | 34 ++ .../dist/test/serveSinglePageApp.d.ts | 1 + .../dist/test/serveSinglePageApp.js | 34 ++ .../@cloudflare/kv-asset-handler/dist/types.d.ts | 28 ++ .../@cloudflare/kv-asset-handler/dist/types.js | 31 ++ 14 files changed, 1062 insertions(+) create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/index.d.ts create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/index.js create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/mocks.d.ts create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/mocks.js create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.d.ts create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.js create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/types.d.ts create mode 100644 node_modules/@cloudflare/kv-asset-handler/dist/types.js (limited to 'node_modules/@cloudflare/kv-asset-handler/dist') diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/index.d.ts b/node_modules/@cloudflare/kv-asset-handler/dist/index.d.ts new file mode 100644 index 0000000..7820e62 --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/index.d.ts @@ -0,0 +1,36 @@ +import { Options, CacheControl, MethodNotAllowedError, NotFoundError, InternalError } from './types'; +declare global { + var __STATIC_CONTENT: any, __STATIC_CONTENT_MANIFEST: string; +} +/** + * maps the path of incoming request to the request pathKey to look up + * in bucket and in cache + * e.g. for a path '/' returns '/index.html' which serves + * the content of bucket/index.html + * @param {Request} request incoming request + */ +declare const mapRequestToAsset: (request: Request, options?: Partial) => Request; +/** + * maps the path of incoming request to /index.html if it evaluates to + * any HTML file. + * @param {Request} request incoming request + */ +declare function serveSinglePageApp(request: Request, options?: Partial): Request; +/** + * 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 + * */ +declare type Evt = { + request: Request; + waitUntil: (promise: Promise) => void; +}; +declare const getAssetFromKV: (event: Evt, options?: Partial) => Promise; +export { getAssetFromKV, mapRequestToAsset, serveSinglePageApp }; +export { Options, CacheControl, MethodNotAllowedError, NotFoundError, InternalError }; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/index.js b/node_modules/@cloudflare/kv-asset-handler/dist/index.js new file mode 100644 index 0000000..5f80b5e --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/index.js @@ -0,0 +1,267 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InternalError = exports.NotFoundError = exports.MethodNotAllowedError = exports.serveSinglePageApp = exports.mapRequestToAsset = exports.getAssetFromKV = void 0; +const mime = require("mime"); +const types_1 = require("./types"); +Object.defineProperty(exports, "MethodNotAllowedError", { enumerable: true, get: function () { return types_1.MethodNotAllowedError; } }); +Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return types_1.NotFoundError; } }); +Object.defineProperty(exports, "InternalError", { enumerable: true, get: function () { return types_1.InternalError; } }); +const defaultCacheControl = { + browserTTL: null, + edgeTTL: 2 * 60 * 60 * 24, + bypassCache: false, // do not bypass Cloudflare's cache +}; +const parseStringAsObject = (maybeString) => typeof maybeString === 'string' ? JSON.parse(maybeString) : maybeString; +const getAssetFromKVDefaultOptions = { + ASSET_NAMESPACE: typeof __STATIC_CONTENT !== 'undefined' ? __STATIC_CONTENT : undefined, + ASSET_MANIFEST: typeof __STATIC_CONTENT_MANIFEST !== 'undefined' + ? parseStringAsObject(__STATIC_CONTENT_MANIFEST) + : {}, + cacheControl: defaultCacheControl, + defaultMimeType: 'text/plain', + defaultDocument: 'index.html', + pathIsEncoded: false, +}; +function assignOptions(options) { + // Assign any missing options passed in to the default + // options.mapRequestToAsset is handled manually later + return Object.assign({}, getAssetFromKVDefaultOptions, options); +} +/** + * maps the path of incoming request to the request pathKey to look up + * in bucket and in cache + * e.g. for a path '/' returns '/index.html' which serves + * the content of bucket/index.html + * @param {Request} request incoming request + */ +const mapRequestToAsset = (request, options) => { + options = assignOptions(options); + const parsedUrl = new URL(request.url); + let pathname = parsedUrl.pathname; + if (pathname.endsWith('/')) { + // If path looks like a directory append options.defaultDocument + // e.g. If path is /about/ -> /about/index.html + pathname = pathname.concat(options.defaultDocument); + } + else if (!mime.getType(pathname)) { + // If path doesn't look like valid content + // e.g. /about.me -> /about.me/index.html + pathname = pathname.concat('/' + options.defaultDocument); + } + parsedUrl.pathname = pathname; + return new Request(parsedUrl.toString(), request); +}; +exports.mapRequestToAsset = mapRequestToAsset; +/** + * maps the path of incoming request to /index.html if it evaluates to + * any HTML file. + * @param {Request} request incoming request + */ +function serveSinglePageApp(request, options) { + options = assignOptions(options); + // First apply the default handler, which already has logic to detect + // paths that should map to HTML files. + request = mapRequestToAsset(request, options); + const parsedUrl = new URL(request.url); + // Detect if the default handler decided to map to + // a HTML file in some specific directory. + if (parsedUrl.pathname.endsWith('.html')) { + // If expected HTML file was missing, just return the root index.html (or options.defaultDocument) + return new Request(`${parsedUrl.origin}/${options.defaultDocument}`, request); + } + else { + // The default handler decided this is not an HTML page. It's probably + // an image, CSS, or JS file. Leave it as-is. + return request; + } +} +exports.serveSinglePageApp = serveSinglePageApp; +const getAssetFromKV = async (event, options) => { + options = assignOptions(options); + const request = event.request; + const ASSET_NAMESPACE = options.ASSET_NAMESPACE; + const ASSET_MANIFEST = parseStringAsObject(options.ASSET_MANIFEST); + if (typeof ASSET_NAMESPACE === 'undefined') { + throw new types_1.InternalError(`there is no KV namespace bound to the script`); + } + const rawPathKey = new URL(request.url).pathname.replace(/^\/+/, ''); // strip any preceding /'s + let pathIsEncoded = options.pathIsEncoded; + let requestKey; + // if options.mapRequestToAsset is explicitly passed in, always use it and assume user has own intentions + // otherwise handle request as normal, with default mapRequestToAsset below + if (options.mapRequestToAsset) { + requestKey = options.mapRequestToAsset(request); + } + else if (ASSET_MANIFEST[rawPathKey]) { + requestKey = request; + } + else if (ASSET_MANIFEST[decodeURIComponent(rawPathKey)]) { + pathIsEncoded = true; + requestKey = request; + } + else { + const mappedRequest = mapRequestToAsset(request); + const mappedRawPathKey = new URL(mappedRequest.url).pathname.replace(/^\/+/, ''); + if (ASSET_MANIFEST[decodeURIComponent(mappedRawPathKey)]) { + pathIsEncoded = true; + requestKey = mappedRequest; + } + else { + // use default mapRequestToAsset + requestKey = mapRequestToAsset(request, options); + } + } + const SUPPORTED_METHODS = ['GET', 'HEAD']; + if (!SUPPORTED_METHODS.includes(requestKey.method)) { + throw new types_1.MethodNotAllowedError(`${requestKey.method} is not a valid request method`); + } + const parsedUrl = new URL(requestKey.url); + const pathname = pathIsEncoded ? decodeURIComponent(parsedUrl.pathname) : parsedUrl.pathname; // decode percentage encoded path only when necessary + // pathKey is the file path to look up in the manifest + let pathKey = pathname.replace(/^\/+/, ''); // remove prepended / + // @ts-ignore + const cache = caches.default; + let mimeType = mime.getType(pathKey) || options.defaultMimeType; + if (mimeType.startsWith('text') || mimeType === 'application/javascript') { + mimeType += '; charset=utf-8'; + } + let shouldEdgeCache = false; // false if storing in KV by raw file path i.e. no hash + // check manifest for map from file path to hash + if (typeof ASSET_MANIFEST !== 'undefined') { + if (ASSET_MANIFEST[pathKey]) { + pathKey = ASSET_MANIFEST[pathKey]; + // if path key is in asset manifest, we can assume it contains a content hash and can be cached + shouldEdgeCache = true; + } + } + // TODO this excludes search params from cache, investigate ideal behavior + let cacheKey = new Request(`${parsedUrl.origin}/${pathKey}`, request); + // if argument passed in for cacheControl is a function then + // evaluate that function. otherwise return the Object passed in + // or default Object + const evalCacheOpts = (() => { + switch (typeof options.cacheControl) { + case 'function': + return options.cacheControl(request); + case 'object': + return options.cacheControl; + default: + return defaultCacheControl; + } + })(); + // formats the etag depending on the response context. if the entityId + // is invalid, returns an empty string (instead of null) to prevent the + // the potentially disastrous scenario where the value of the Etag resp + // header is "null". Could be modified in future to base64 encode etc + const formatETag = (entityId = pathKey, validatorType = 'strong') => { + if (!entityId) { + return ''; + } + switch (validatorType) { + case 'weak': + if (!entityId.startsWith('W/')) { + return `W/${entityId}`; + } + return entityId; + case 'strong': + if (entityId.startsWith(`W/"`)) { + entityId = entityId.replace('W/', ''); + } + if (!entityId.endsWith(`"`)) { + entityId = `"${entityId}"`; + } + return entityId; + default: + return ''; + } + }; + options.cacheControl = Object.assign({}, defaultCacheControl, evalCacheOpts); + // override shouldEdgeCache if options say to bypassCache + if (options.cacheControl.bypassCache || + options.cacheControl.edgeTTL === null || + request.method == 'HEAD') { + shouldEdgeCache = false; + } + // only set max-age if explicitly passed in a number as an arg + const shouldSetBrowserCache = typeof options.cacheControl.browserTTL === 'number'; + let response = null; + if (shouldEdgeCache) { + response = await cache.match(cacheKey); + } + if (response) { + if (response.status > 300 && response.status < 400) { + if (response.body && 'cancel' in Object.getPrototypeOf(response.body)) { + // Body exists and environment supports readable streams + response.body.cancel(); + } + else { + // Environment doesnt support readable streams, or null repsonse body. Nothing to do + } + response = new Response(null, response); + } + else { + // fixes #165 + let opts = { + headers: new Headers(response.headers), + status: 0, + statusText: '', + }; + opts.headers.set('cf-cache-status', 'HIT'); + if (response.status) { + opts.status = response.status; + opts.statusText = response.statusText; + } + else if (opts.headers.has('Content-Range')) { + opts.status = 206; + opts.statusText = 'Partial Content'; + } + else { + opts.status = 200; + opts.statusText = 'OK'; + } + response = new Response(response.body, opts); + } + } + else { + const body = await ASSET_NAMESPACE.get(pathKey, 'arrayBuffer'); + if (body === null) { + throw new types_1.NotFoundError(`could not find ${pathKey} in your content namespace`); + } + response = new Response(body); + if (shouldEdgeCache) { + response.headers.set('Accept-Ranges', 'bytes'); + response.headers.set('Content-Length', body.length); + // set etag before cache insertion + if (!response.headers.has('etag')) { + response.headers.set('etag', formatETag(pathKey, 'strong')); + } + // determine Cloudflare cache behavior + response.headers.set('Cache-Control', `max-age=${options.cacheControl.edgeTTL}`); + event.waitUntil(cache.put(cacheKey, response.clone())); + response.headers.set('CF-Cache-Status', 'MISS'); + } + } + response.headers.set('Content-Type', mimeType); + if (response.status === 304) { + let etag = formatETag(response.headers.get('etag'), 'strong'); + let ifNoneMatch = cacheKey.headers.get('if-none-match'); + let proxyCacheStatus = response.headers.get('CF-Cache-Status'); + if (etag) { + if (ifNoneMatch && ifNoneMatch === etag && proxyCacheStatus === 'MISS') { + response.headers.set('CF-Cache-Status', 'EXPIRED'); + } + else { + response.headers.set('CF-Cache-Status', 'REVALIDATED'); + } + response.headers.set('etag', formatETag(etag, 'weak')); + } + } + if (shouldSetBrowserCache) { + response.headers.set('Cache-Control', `max-age=${options.cacheControl.browserTTL}`); + } + else { + response.headers.delete('Cache-Control'); + } + return response; +}; +exports.getAssetFromKV = getAssetFromKV; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/mocks.d.ts b/node_modules/@cloudflare/kv-asset-handler/dist/mocks.d.ts new file mode 100644 index 0000000..f829335 --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/mocks.d.ts @@ -0,0 +1,14 @@ +export declare const getEvent: (request: Request) => any; +export declare const mockKV: (store: any) => { + get: (path: string) => any; +}; +export declare const mockManifest: () => string; +export declare const mockCaches: () => { + default: { + match(key: any): Promise; + put(key: any, val: Response): Promise; + }; +}; +export declare function mockRequestScope(): void; +export declare function mockGlobalScope(): void; +export declare const sleep: (milliseconds: number) => Promise; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/mocks.js b/node_modules/@cloudflare/kv-asset-handler/dist/mocks.js new file mode 100644 index 0000000..0f7a338 --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/mocks.js @@ -0,0 +1,146 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sleep = exports.mockGlobalScope = exports.mockRequestScope = exports.mockCaches = exports.mockManifest = exports.mockKV = exports.getEvent = void 0; +const makeServiceWorkerEnv = require('service-worker-mock'); +const HASH = '123HASHBROWN'; +const getEvent = (request) => { + const waitUntil = async (callback) => { + await callback; + }; + return { + request, + waitUntil, + }; +}; +exports.getEvent = getEvent; +const store = { + 'key1.123HASHBROWN.txt': 'val1', + 'key1.123HASHBROWN.png': 'val1', + 'index.123HASHBROWN.html': 'index.html', + 'cache.123HASHBROWN.html': 'cache me if you can', + '测试.123HASHBROWN.html': 'My filename is non-ascii', + '%not-really-percent-encoded.123HASHBROWN.html': 'browser percent encoded', + '%2F.123HASHBROWN.html': 'user percent encoded', + '你好.123HASHBROWN.html': 'I shouldnt be served', + '%E4%BD%A0%E5%A5%BD.123HASHBROWN.html': 'Im important', + 'nohash.txt': 'no hash but still got some result', + 'sub/blah.123HASHBROWN.png': 'picturedis', + 'sub/index.123HASHBROWN.html': 'picturedis', + 'client.123HASHBROWN': 'important file', + 'client.123HASHBROWN/index.html': 'Im here but serve my big bro above', + 'image.123HASHBROWN.png': 'imagepng', + 'image.123HASHBROWN.webp': 'imagewebp', + '你好/index.123HASHBROWN.html': 'My path is non-ascii', +}; +const mockKV = (store) => { + return { + get: (path) => store[path] || null, + }; +}; +exports.mockKV = mockKV; +const mockManifest = () => { + return JSON.stringify({ + 'key1.txt': `key1.${HASH}.txt`, + 'key1.png': `key1.${HASH}.png`, + 'cache.html': `cache.${HASH}.html`, + '测试.html': `测试.${HASH}.html`, + '你好.html': `你好.${HASH}.html`, + '%not-really-percent-encoded.html': `%not-really-percent-encoded.${HASH}.html`, + '%2F.html': `%2F.${HASH}.html`, + '%E4%BD%A0%E5%A5%BD.html': `%E4%BD%A0%E5%A5%BD.${HASH}.html`, + 'index.html': `index.${HASH}.html`, + 'sub/blah.png': `sub/blah.${HASH}.png`, + 'sub/index.html': `sub/index.${HASH}.html`, + client: `client.${HASH}`, + 'client/index.html': `client.${HASH}`, + 'image.png': `image.${HASH}.png`, + 'image.webp': `image.${HASH}.webp`, + '你好/index.html': `你好/index.${HASH}.html`, + }); +}; +exports.mockManifest = mockManifest; +let cacheStore = new Map(); +const mockCaches = () => { + return { + default: { + 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 { + response.status = 200; + } + let etag = response.headers.get('etag'); + if (etag && !etag.includes('W/')) { + response.headers.set('etag', `W/${etag}`); + } + } + return response; + }, + 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; + }, + }, + }; +}; +exports.mockCaches = mockCaches; +// mocks functionality used inside worker request +function mockRequestScope() { + Object.assign(global, makeServiceWorkerEnv()); + 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)() }); +} +exports.mockRequestScope = mockRequestScope; +// mocks functionality used on global isolate scope. such as the KV namespace bind +function mockGlobalScope() { + Object.assign(global, { __STATIC_CONTENT_MANIFEST: (0, exports.mockManifest)() }); + Object.assign(global, { __STATIC_CONTENT: (0, exports.mockKV)(store) }); +} +exports.mockGlobalScope = mockGlobalScope; +const sleep = (milliseconds) => { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +}; +exports.sleep = sleep; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.d.ts b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.js b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.js new file mode 100644 index 0000000..83838ad --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV-optional.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = require("ava"); +const mocks_1 = require("../mocks"); +(0, mocks_1.mockGlobalScope)(); +// manually reset manifest global, to test optional behaviour +Object.assign(global, { __STATIC_CONTENT_MANIFEST: undefined }); +const index_1 = require("../index"); +(0, ava_1.default)('getAssetFromKV return correct val from KV without manifest', async (t) => { + (0, mocks_1.mockRequestScope)(); + // manually reset manifest global, to test optional behaviour + Object.assign(global, { __STATIC_CONTENT_MANIFEST: undefined }); + const event = (0, mocks_1.getEvent)(new Request('https://blah.com/key1.123HASHBROWN.txt')); + const res = await (0, index_1.getAssetFromKV)(event); + if (res) { + t.is(await res.text(), 'val1'); + t.true(res.headers.get('content-type').includes('text')); + } + else { + t.fail('Response was undefined'); + } +}); diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js new file mode 100644 index 0000000..724c1d1 --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js @@ -0,0 +1,446 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = require("ava"); +const mocks_1 = require("../mocks"); +(0, mocks_1.mockGlobalScope)(); +const index_1 = require("../index"); +(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(await res.text(), 'val1'); + t.true(res.headers.get('content-type').includes('text')); + } + else { + t.fail('Response was undefined'); + } +}); +(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')); +}); +(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')); +}); +(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(await res.text(), 'no hash but still got some result'); + t.true(res.headers.get('content-type').includes('text')); + } + else { + t.fail('Response was undefined'); + } +}); +(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); +}); +(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(await res.text(), 'picturedis'); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res.text(), 'index.html'); + t.true(res.headers.get('content-type').includes('html')); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res.text(), 'My filename is non-ascii'); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res.text(), 'browser percent encoded'); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res.text(), 'user percent encoded'); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res1.text(), 'Im important'); + t.is(await res2.text(), 'Im important'); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res1.text(), 'My path is non-ascii'); + t.is(await res2.text(), 'My path is non-ascii'); + } + else { + t.fail('Response was undefined'); + } +}); +(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 = (0, index_1.mapRequestToAsset)(request); + let url = new URL(defaultModifiedRequest.url); + url.pathname = url.pathname.replace('/docs', ''); + return new Request(url.toString(), request); + }; + const res = await (0, index_1.getAssetFromKV)(event, { mapRequestToAsset: customRequestMapper }); + if (res) { + t.is(await res.text(), 'picturedis'); + } + else { + t.fail('Response was undefined'); + } +}); +(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 + (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 = (0, index_1.mapRequestToAsset)(request); + let url = new URL(defaultModifiedRequest.url); + url.pathname = '/image.webp'; // other different file in manifest + return new Request(url.toString(), request); + }; + const res = await (0, index_1.getAssetFromKV)(event, { mapRequestToAsset: customRequestMapper }); + if (res) { + t.is(await res.text(), 'imagewebp'); + } + else { + t.fail('Response was undefined'); + } +}); +(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) { + t.is(res.headers.get('cache-control'), 'max-age=22'); + } + else { + t.fail('Response was undefined'); + } +}); +(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) => { + if (new URL(req.url).pathname.endsWith('.png')) + return { + browserTTL: 720, + edgeTTL: 720, + }; + else + return { + bypassCache: true, + }; + }; + const res1 = await (0, index_1.getAssetFromKV)(event1, { cacheControl: cacheOnlyPngs }); + const res2 = await (0, index_1.getAssetFromKV)(event2, { cacheControl: cacheOnlyPngs }); + if (res1 && res2) { + t.is(res1.headers.get('cache-control'), null); + t.true(res2.headers.get('content-type').includes('png')); + t.is(res2.headers.get('cache-control'), 'max-age=720'); + t.is(res2.headers.get('cf-cache-status'), 'MISS'); + } + else { + t.fail('Response was undefined'); + } +}); +(0, ava_1.default)('getAssetFromKV caches on two sequential requests', async (t) => { + (0, mocks_1.mockRequestScope)(); + const resourceKey = 'cache.html'; + 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: { + 'if-none-match': `"${resourceVersion}"`, + }, + })); + 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) { + t.is(res1.headers.get('cf-cache-status'), 'MISS'); + t.is(res1.headers.get('cache-control'), 'max-age=720'); + t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED'); + } + else { + t.fail('Response was undefined'); + } +}); +(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((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: { + 'if-none-match': `"${resourceVersion}"`, + }, + })); + 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) { + t.is(res1.headers.get('cf-cache-status'), 'MISS'); + t.is(res1.headers.get('cache-control'), null); + t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED'); + t.is(res2.headers.get('cache-control'), null); + } + else { + t.fail('Response was undefined'); + } +}); +(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) { + t.is(res.headers.get('cache-control'), null); + t.is(res.headers.get('cf-cache-status'), null); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res.text(), 'index.html'); + } + else { + t.fail('Response was undefined'); + } +}); +(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(await res.text(), 'picturedis'); + } + else { + t.fail('Response was undefined'); + } +}); +(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); +}); +(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) { + t.is(res1.headers.get('cf-cache-status'), null); + t.is(res1.headers.get('cache-control'), null); + t.is(res2.headers.get('cf-cache-status'), null); + t.is(res2.headers.get('cache-control'), null); + } + else { + t.fail('Response was undefined'); + } +}); +(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 = (0, mocks_1.getEvent)(new Request('https://blah.com/')); + const res = await (0, index_1.getAssetFromKV)(event); + if (res) { + t.is(await res.text(), 'index.html'); + t.true(res.headers.get('content-type').includes('html')); + } + else { + t.fail('Response was undefined'); + } +}); +(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 = (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); +}); +(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 = (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); +}); +(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((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: { + 'if-none-match': `W/"${resourceVersion}"`, + }, + })); + 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) { + t.is(res1.headers.get('cf-cache-status'), 'MISS'); + t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED'); + } + else { + t.fail('Response was undefined'); + } +}); +(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((0, mocks_1.mockManifest)())[resourceKey]; + const req1 = new Request(`https://blah.com/${resourceKey}`, { + headers: { + 'if-none-match': `"${resourceVersion}"`, + }, + }); + const req2 = new Request(`https://blah.com/${resourceKey}`, { + headers: { + 'if-none-match': `"${resourceVersion}-another-version"`, + }, + }); + 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) { + t.is(res1.headers.get('cf-cache-status'), 'MISS'); + t.is(res2.headers.get('etag'), `W/${req1.headers.get('if-none-match')}`); + t.is(res2.headers.get('cf-cache-status'), 'REVALIDATED'); + t.not(res3.headers.get('etag'), req2.headers.get('if-none-match')); + t.is(res3.headers.get('cf-cache-status'), 'MISS'); + } + else { + t.fail('Response was undefined'); + } +}); +(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((0, mocks_1.mockManifest)())[resourceKey]; + const req1 = new Request(`https://blah.com/${resourceKey}`, { + headers: { + 'if-none-match': `"${resourceVersion}"`, + }, + }); + 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) { + t.is(res1.headers.get('cf-cache-status'), 'MISS'); + t.is(res2.headers.get('etag'), `W/${req1.headers.get('if-none-match')}`); + } + else { + t.fail('Response was undefined'); + } +}); +(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 = (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) { + t.is(res1.headers.get('cf-cache-status'), 'MISS'); + t.is(res1.headers.get('cache-control'), null); + t.is(res2.status, 200); + t.is(res2.headers.get('cf-cache-status'), 'HIT'); + } + else { + t.fail('Response was undefined'); + } +}); +(0, ava_1.default)('getAssetFromKV if range request submitted and resource in cache, request fulfilled', async (t) => { + const resourceKey = 'cache.html'; + 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')) { + t.is(res2.status, 206); + } + else { + t.fail('Response was undefined'); + } +}); +ava_1.default.todo('getAssetFromKV when body not empty, should invoke .cancel()'); diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts b/node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js b/node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js new file mode 100644 index 0000000..11be912 --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js @@ -0,0 +1,34 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = require("ava"); +const mocks_1 = require("../mocks"); +(0, mocks_1.mockGlobalScope)(); +const index_1 = require("../index"); +(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 = (0, index_1.mapRequestToAsset)(request); + t.is(newRequest.url, request.url + '/index.html'); +}); +(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 = (0, index_1.mapRequestToAsset)(request); + t.is(newRequest.url, request.url + 'index.html'); +}); +(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 = (0, index_1.mapRequestToAsset)(request); + t.is(newRequest.url, request.url + 'index.html'); +}); +(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 = (0, index_1.mapRequestToAsset)(request, { defaultDocument: 'default.html' }); + t.is(newRequest.url, request.url + '/default.html'); +}); diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts b/node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js b/node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js new file mode 100644 index 0000000..8ee0bfb --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js @@ -0,0 +1,34 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ava_1 = require("ava"); +const mocks_1 = require("../mocks"); +(0, mocks_1.mockGlobalScope)(); +const index_1 = require("../index"); +function testRequest(path) { + (0, mocks_1.mockRequestScope)(); + let url = new URL('https://example.com'); + url.pathname = path; + let request = new Request(url.toString()); + return request; +} +(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 = (0, index_1.serveSinglePageApp)(request); + t.deepEqual(expected_request, actual_request); +}); +(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 = (0, index_1.serveSinglePageApp)(request); + t.deepEqual(expected_request, actual_request); +}); +(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 = (0, index_1.serveSinglePageApp)(request); + t.deepEqual(expected_request, actual_request); +}); diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/types.d.ts b/node_modules/@cloudflare/kv-asset-handler/dist/types.d.ts new file mode 100644 index 0000000..eef1bff --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/types.d.ts @@ -0,0 +1,28 @@ +export declare type CacheControl = { + browserTTL: number; + edgeTTL: number; + bypassCache: boolean; +}; +export declare type AssetManifestType = Record; +export declare type Options = { + cacheControl: ((req: Request) => Partial) | Partial; + ASSET_NAMESPACE: any; + ASSET_MANIFEST: AssetManifestType | string; + mapRequestToAsset?: (req: Request, options?: Partial) => Request; + defaultMimeType: string; + defaultDocument: string; + pathIsEncoded: boolean; +}; +export declare class KVError extends Error { + constructor(message?: string, status?: number); + status: number; +} +export declare class MethodNotAllowedError extends KVError { + constructor(message?: string, status?: number); +} +export declare class NotFoundError extends KVError { + constructor(message?: string, status?: number); +} +export declare class InternalError extends KVError { + constructor(message?: string, status?: number); +} diff --git a/node_modules/@cloudflare/kv-asset-handler/dist/types.js b/node_modules/@cloudflare/kv-asset-handler/dist/types.js new file mode 100644 index 0000000..ba08c1b --- /dev/null +++ b/node_modules/@cloudflare/kv-asset-handler/dist/types.js @@ -0,0 +1,31 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InternalError = exports.NotFoundError = exports.MethodNotAllowedError = exports.KVError = void 0; +class KVError extends Error { + constructor(message, status = 500) { + super(message); + // see: typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html + Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain + this.name = KVError.name; // stack traces display correctly now + this.status = status; + } +} +exports.KVError = KVError; +class MethodNotAllowedError extends KVError { + constructor(message = `Not a valid request method`, status = 405) { + super(message, status); + } +} +exports.MethodNotAllowedError = MethodNotAllowedError; +class NotFoundError extends KVError { + constructor(message = `Not Found`, status = 404) { + super(message, status); + } +} +exports.NotFoundError = NotFoundError; +class InternalError extends KVError { + constructor(message = `Internal Error in KV Asset Handler`, status = 500) { + super(message, status); + } +} +exports.InternalError = InternalError; -- cgit v1.2.3