├── .github └── workflows │ └── ci.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── fixtures ├── 1MB.file ├── lorem.txt └── test.json ├── mod.ts ├── polyfill.ts └── test.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | name: test-${{ matrix.deno }}-${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | deno: [old, stable, canary] 17 | os: [macOS-latest, windows-latest, ubuntu-latest] 18 | 19 | steps: 20 | - name: Setup repo 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup Deno 24 | run: | 25 | curl -fsSL https://deno.land/x/install/install.sh | sh ${{ matrix.deno == 'old' && '-s v1.8.0' || '' }} 26 | echo "$HOME/.deno/bin" >> $${{ runner.os == 'Windows' && 'env:' || '' }}GITHUB_PATH 27 | 28 | - name: Upgrade to Deno canary 29 | if: matrix.deno == 'canary' 30 | run: deno upgrade --canary 31 | 32 | - name: Format 33 | if: runner.os == 'Linux' 34 | run: deno fmt --check 35 | 36 | - name: Lint 37 | if: runner.os == 'Linux' 38 | run: deno lint --unstable 39 | 40 | - name: Run Tests 41 | run: deno test -A 42 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Luca Casonato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deno_local_file_fetch 2 | 3 | This polyfills file:// support for `fetch`. 4 | -------------------------------------------------------------------------------- /fixtures/lorem.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam egestas dolor vel nibh interdum ornare. Phasellus ultrices mattis ipsum eu interdum. Vestibulum fringilla dapibus mattis. Donec tristique vel ligula vitae tempor. In eleifend molestie interdum. Curabitur finibus non sapien non iaculis. In ut justo maximus, viverra leo ut, pulvinar eros. Praesent fermentum, metus sed lobortis pharetra, mauris neque volutpat justo, in efficitur diam magna egestas quam. Quisque lacinia fringilla faucibus. Integer at nunc metus. Cras feugiat sem odio, ut varius est congue et. 2 | 3 | Duis vel consequat risus, quis interdum nisl. Donec rutrum lectus eget est consectetur congue. Nunc volutpat elementum fermentum. Donec pulvinar arcu non iaculis ultrices. Aliquam nec leo mauris. Duis tellus ipsum, finibus id mi porta, ornare gravida arcu. Donec eu efficitur tellus. Nunc sollicitudin tempus sem vitae facilisis. Suspendisse luctus, lectus non efficitur molestie, dui elit imperdiet tortor, dapibus rutrum orci lorem non dolor. Suspendisse purus enim, dapibus a urna molestie, blandit porta dolor. Fusce ut lectus ut sem porta mollis. Aenean id elementum lacus, eu bibendum mi. Vivamus commodo iaculis metus, ac vehicula dolor ultricies vitae. 4 | 5 | Nulla facilisi. Cras a volutpat lorem. Sed vel leo rutrum lectus tincidunt mattis non in eros. Vestibulum luctus cursus felis eu porttitor. Sed sed molestie justo. Nulla ut ex nec tellus interdum aliquam. Pellentesque ac orci a dolor aliquet maximus. Vestibulum vel ultricies massa, vel fringilla risus. In hac habitasse platea dictumst. 6 | 7 | Mauris nec feugiat turpis, quis iaculis risus. Integer bibendum lectus et quam ultricies malesuada. Fusce tincidunt consectetur ipsum a tincidunt. Proin hendrerit enim at sollicitudin dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ligula massa, maximus non imperdiet nec, tempus id risus. Nulla fringilla tincidunt ultricies. Cras a feugiat magna. Nulla pretium purus quis ultricies scelerisque. Pellentesque eu nunc at sem mollis maximus sit amet luctus eros. Proin facilisis, ipsum sit amet consequat tincidunt, diam eros accumsan nisi, a posuere augue urna pulvinar diam. Phasellus eu nibh neque. Sed eget imperdiet mauris, ac tristique eros. 8 | 9 | Vivamus aliquet massa in eleifend auctor. Cras rutrum feugiat nulla, ac semper dolor congue a. Ut a massa urna. Etiam at urna tempus, congue neque eu, auctor sapien. Sed viverra, augue vel euismod dictum, mauris dolor porta mauris, sed mattis ante magna vitae nunc. Suspendisse sagittis, nunc id vehicula sollicitudin, dui tellus iaculis massa, sed imperdiet felis metus ac nisl. Fusce tristique ultrices felis, et facilisis lectus mattis sed. Ut iaculis, velit a accumsan efficitur, ipsum purus facilisis neque, eget consectetur lorem magna non felis. Donec facilisis ac ante vitae consequat. Nullam sagittis posuere volutpat. Pellentesque nec feugiat nulla, sit amet rutrum nulla. -------------------------------------------------------------------------------- /fixtures/test.json: -------------------------------------------------------------------------------- 1 | { "hello": "world" } 2 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | import { lookup } from "https://deno.land/x/media_types@v2.8.4/mod.ts"; 2 | import { iter } from "https://deno.land/std@0.97.0/io/util.ts"; 3 | 4 | const originalfetch = globalThis.fetch; 5 | 6 | async function fetch( 7 | input: string | Request | URL, 8 | init?: RequestInit, 9 | ): Promise { 10 | const url = typeof input === "string" 11 | ? new URL(input) 12 | : input instanceof Request 13 | ? new URL(input.url) 14 | : input; 15 | 16 | if (url.protocol === "file:") { 17 | // Only allow GET requests 18 | if (init && init.method && init.method !== "GET") { 19 | throw new TypeError( 20 | `${init.method} is not a supported method for file:// URLs.`, 21 | ); 22 | } 23 | 24 | // Open the file, and convert to ReadableStream 25 | const file = await Deno.open(url, { read: true }).catch((err) => { 26 | if (err instanceof Deno.errors.NotFound) { 27 | return undefined; 28 | } else { 29 | throw err; 30 | } 31 | }); 32 | if (!file) { 33 | return new Response("404 not found", { status: 404 }); 34 | } 35 | const body = new ReadableStream({ 36 | start: async (controller) => { 37 | for await (const chunk of iter(file)) { 38 | controller.enqueue(chunk.slice(0)); 39 | } 40 | file.close(); 41 | controller.close(); 42 | }, 43 | cancel() { 44 | file.close(); 45 | }, 46 | }); 47 | 48 | // Get meta information 49 | const headers = new Headers(); 50 | const contentType = lookup(url.pathname); 51 | if (contentType) { 52 | headers.set("content-type", contentType); 53 | } 54 | const info = await Deno.stat(url); 55 | if (info.mtime) { 56 | headers.set("last-modified", info.mtime.toUTCString()); 57 | } 58 | 59 | // Create 200 streaming response 60 | const response = new Response(body, { status: 200, headers }); 61 | Object.defineProperty(response, "url", { 62 | get() { 63 | return url; 64 | }, 65 | configurable: true, 66 | enumerable: true, 67 | }); 68 | return response; 69 | } 70 | 71 | return originalfetch(input, init); 72 | } 73 | 74 | export { fetch }; 75 | -------------------------------------------------------------------------------- /polyfill.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from "./mod.ts"; 2 | 3 | globalThis.fetch = fetch; 4 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import "./polyfill.ts"; 2 | import { assertEquals } from "https://deno.land/std@0.97.0/testing/asserts.ts"; 3 | import { toFileUrl } from "https://deno.land/std@0.97.0/path/mod.ts"; 4 | 5 | Deno.test("fetch local file URL", async () => { 6 | const req = await fetch(new URL("./fixtures/test.json", import.meta.url)); 7 | assertEquals(req.status, 200); 8 | assertEquals(req.headers.get("content-type"), "application/json"); 9 | const json = await req.json(); 10 | assertEquals(json, { hello: "world" }); 11 | }); 12 | 13 | Deno.test("fetch local file URL (larger)", async () => { 14 | const lorem = (await Deno.readTextFile("./fixtures/lorem.txt")).repeat(32); 15 | const tmp = await Deno.makeTempFile(); 16 | await Deno.writeTextFile(tmp, lorem); 17 | 18 | const response = await fetch(toFileUrl(tmp)); 19 | const text = await response.text(); 20 | await Deno.remove(tmp); 21 | assertEquals(text, lorem); 22 | }); 23 | 24 | Deno.test("fetch 1MB file", async () => { 25 | const { size } = await Deno.stat("fixtures/1MB.file"); 26 | const response = await fetch(new URL("./fixtures/1MB.file", import.meta.url)); 27 | const file = await response.blob(); 28 | 29 | assertEquals(size, file.size); 30 | }); 31 | --------------------------------------------------------------------------------