From d43d584a3cadaee8f2df1cb5a54a4cd43ce772a4 Mon Sep 17 00:00:00 2001 From: huangse199 Date: Mon, 1 Jun 2026 21:28:35 +0800 Subject: [PATCH] Document and cover file download endpoints Add focused coverage for both download URL styles and document the browser-friendly attachment endpoints so issue #5 is verifiable. Co-Authored-By: Claude Opus 4.7 --- README.md | 17 +++++++++++++++++ bun.lock | 3 ++- routes/index.ts | 2 ++ tests/routes/files.test.ts | 30 +++++++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 993d309..79cc4d7 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,23 @@ Response: { } ``` +#### Download File + +```http +GET /files/download?file_id=1 +# or +GET /files/download?file_path=path/to/file.txt +# or +GET /files/download/path/to/file.txt + +Response: raw file contents with a Content-Disposition attachment header. +``` + +Use the download endpoints when you want a browser-friendly attachment URL +instead of the JSON metadata returned by `/files/get`. Text files are returned as +`text/plain`; binary files are decoded from `binary_content_b64` and returned as +`application/octet-stream` with a `Content-Length` header. + #### List Files ```http diff --git a/bun.lock b/bun.lock index 7098c88..b5122f6 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,6 @@ "name": "@tscircuit/file-server", "dependencies": { "winterspec": "^0.0.86", - "zod": "^3.23.8", "zustand": "^4.5.5", "zustand-hoist": "^2.0.1", }, @@ -16,9 +15,11 @@ "next": "^14.2.5", "redaxios": "^0.5.1", "tsup": "^8.3.5", + "zod": "^3.23.8", }, "peerDependencies": { "typescript": "^5.0.0", + "zod": "*", }, }, }, diff --git a/routes/index.ts b/routes/index.ts index ade1972..b3bc6a9 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -13,6 +13,8 @@ This is a simple file server API, it has the following API: /health - Health check /files/get?file_path=... - Get a file +/files/download?file_path=... - Download a file as an attachment +/files/download/path/to/file.txt - Download a file path as an attachment /files/list - List all files /files/upsert - Upsert a file diff --git a/tests/routes/files.test.ts b/tests/routes/files.test.ts index 196c354..8ba36e4 100644 --- a/tests/routes/files.test.ts +++ b/tests/routes/files.test.ts @@ -65,7 +65,7 @@ test("binary file operations", async () => { test("file download operations", async () => { const { axios } = await getTestServer() - await axios.post("/files/upsert", { + const createRes = await axios.post("/files/upsert", { file_path: "/download-test.txt", text_content: "Test download content", }) @@ -80,6 +80,15 @@ test("file download operations", async () => { 'attachment; filename="download-test.txt"', ) + const downloadByIdRes = await axios.get("/files/download", { + params: { file_id: createRes.data.file.file_id }, + }) + expect(downloadByIdRes.status).toBe(200) + expect(downloadByIdRes.data).toBe("Test download content") + expect(downloadByIdRes.headers.get("content-disposition")).toBe( + 'attachment; filename="download-test.txt"', + ) + expect( axios.get("/files/download", { params: { file_path: "/missing-file.txt" }, @@ -112,6 +121,25 @@ test("file download operations2", async () => { }) }) +test("nested file download path preserves subdirectories", async () => { + const { axios } = await getTestServer() + + await axios.post("/files/upsert", { + file_path: "/nested/path/download-test3.txt", + text_content: "Nested download content", + }) + + const successRes = await axios.get( + "/files/download/nested/path/download-test3.txt", + ) + expect(successRes.status).toBe(200) + expect(successRes.data).toBe("Nested download content") + expect(successRes.headers.get("content-type")).toBe("text/plain") + expect(successRes.headers.get("content-disposition")).toBe( + 'attachment; filename="download-test3.txt"', + ) +}) + test("file delete operations", async () => { const { axios } = await getTestServer()