@wooksjs/http-static 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,4 @@
1
- # Wooks Serve File
2
-
3
- **!!! This is work-in-progress library, breaking changes are expected !!!**
1
+ # @wooksjs/http-static
4
2
 
5
3
  <p align="center">
6
4
  <img src="../../wooks-logo.png" width="450px"><br>
@@ -9,71 +7,18 @@
9
7
  </a>
10
8
  </p>
11
9
 
12
- Wooks Serve File is composable static file server for [@wooksjs/event-http](https://github.com/wooksjs/wooksjs/tree/main/packages/event-http).
13
-
14
- `serveFile` returns a readable stream and prepares all the neccessary response headers (like content-length, content-type etc).
15
-
16
- - ✅ returns a readable stream
17
- - ✅ prepares all the neccessary response headers (like content-length, content-type etc)
18
- - ✅ can handle etag
19
- - ✅ can handle ranges
10
+ Composable static file server for [@wooksjs/event-http](https://github.com/wooksjs/wooksjs/tree/main/packages/event-http). Serves files as readable streams with automatic content-type detection, ETag support, and range requests.
20
11
 
21
12
  ## Installation
22
13
 
23
- `npm install @wooksjs/http-static`
24
-
25
- ## Usage
26
-
27
- ```js
28
- import { serveFile } from '@wooksjs/http-static'
29
- // ...
30
- serveFile(filePath, options)
14
+ ```sh
15
+ npm install @wooksjs/http-static
31
16
  ```
32
17
 
33
- **serveFile options**
34
-
35
- ```ts
36
- {
37
- // Any header to add
38
- headers?: Record<string, string>,
39
-
40
- // Cache-Control header
41
- cacheControl?: TCacheControl,
42
-
43
- // Expires header
44
- expires?: Date | string | number,
45
-
46
- // when true a header "Pragma: no-cache" will be added
47
- pragmaNoCache?: boolean,
48
-
49
- // the base directory path
50
- baseDir?: string,
51
-
52
- // default extension will be added to the filePath
53
- defaultExt?: string,
54
-
55
- // when true lists files in directory
56
- listDirectory?: boolean,
57
-
58
- // put 'index.html'
59
- // to automatically serve it from the folder
60
- index?: string,
61
- }
62
- ```
63
-
64
- ### Built-in file server example:
65
-
66
- ```js
67
- import { useRouteParams } from 'wooks'
68
- import { serveFile } from '@wooksjs/http-static'
69
- app.get('static/*', () => {
70
- const { get } = useRouteParams()
71
- return serveFile(get('*'), { cacheControl: { maxAge: '10m' } })
72
- })
73
- ```
18
+ ## Documentation
74
19
 
75
- `cacheControl` here is the same object as used in `useSetCacheControl().setCacheControl({ ... })` from `@wooksjs/event-http`
20
+ For full documentation, visit [wooks.moost.org/webapp/static](https://wooks.moost.org/webapp/static).
76
21
 
77
- ## Documentation
22
+ ## License
78
23
 
79
- To check out docs, visit [wooks.moost.org](https://wooks.moost.org/).
24
+ MIT
package/dist/index.cjs CHANGED
@@ -1247,11 +1247,9 @@ const { stat, readdir } = fs.promises;
1247
1247
  * @returns A readable stream, string body, or HTML directory listing.
1248
1248
  */
1249
1249
  async function serveFile(filePath, options = {}) {
1250
- const { status } = (0, __wooksjs_event_http.useResponse)();
1251
- const { setHeader, removeHeader } = (0, __wooksjs_event_http.useSetHeaders)();
1250
+ const response = (0, __wooksjs_event_http.useResponse)();
1252
1251
  const headers = (0, __wooksjs_event_http.useHeaders)();
1253
1252
  const { method, url } = (0, __wooksjs_event_http.useRequest)();
1254
- const { setCacheControl, setExpires, setPragmaNoCache } = (0, __wooksjs_event_http.useSetCacheControl)();
1255
1253
  const normalizedPath = normalizePath(filePath, options.baseDir);
1256
1254
  if (!options.allowDotDot) {
1257
1255
  const resolvedBase = path.default.resolve(options.baseDir || process.cwd());
@@ -1267,7 +1265,7 @@ async function serveFile(filePath, options = {}) {
1267
1265
  }
1268
1266
  throw new __wooksjs_event_http.HttpError(404);
1269
1267
  }
1270
- status(200);
1268
+ response.status = 200;
1271
1269
  const etag = `"${[
1272
1270
  fileStats.ino,
1273
1271
  fileStats.size,
@@ -1275,25 +1273,25 @@ async function serveFile(filePath, options = {}) {
1275
1273
  ].join("-")}"`;
1276
1274
  const lastModified = new Date(fileStats.mtime);
1277
1275
  if (isNotModified(etag, lastModified, headers["if-none-match"] || "", headers["if-modified-since"] || "")) {
1278
- status(304);
1276
+ response.status = 304;
1279
1277
  return "";
1280
1278
  }
1281
- setHeader("etag", etag);
1282
- setHeader("last-modified", lastModified.toUTCString());
1283
- if (options.cacheControl !== void 0) setCacheControl(options.cacheControl);
1284
- if (options.expires) setExpires(options.expires);
1285
- if (options.pragmaNoCache) setPragmaNoCache(options.pragmaNoCache);
1279
+ response.setHeader("etag", etag);
1280
+ response.setHeader("last-modified", lastModified.toUTCString());
1281
+ if (options.cacheControl !== void 0) response.setCacheControl(options.cacheControl);
1282
+ if (options.expires) response.setExpires(options.expires);
1283
+ if (options.pragmaNoCache) response.setPragmaNoCache(options.pragmaNoCache);
1286
1284
  if (fileStats.isDirectory()) {
1287
1285
  if (options.listDirectory) return listDirectory(normalizedPath);
1288
1286
  else if (options.index) {
1289
- if (!filePath.endsWith("/") && url && !url.endsWith("/")) return new __wooksjs_event_http.BaseHttpResponse().setStatus(302).setHeader("location", `${url}/`);
1287
+ if (!filePath.endsWith("/") && url && !url.endsWith("/")) return response.setStatus(302).setHeader("location", `${url}/`);
1290
1288
  return serveFile(path.default.join(filePath, options.index), {
1291
1289
  ...options,
1292
1290
  index: ""
1293
1291
  });
1294
1292
  }
1295
- removeHeader("etag");
1296
- removeHeader("last-modified");
1293
+ response.removeHeader("etag");
1294
+ response.removeHeader("last-modified");
1297
1295
  throw new __wooksjs_event_http.HttpError(404);
1298
1296
  }
1299
1297
  let range = headers.range;
@@ -1312,18 +1310,18 @@ async function serveFile(filePath, options = {}) {
1312
1310
  const ifRangeTag = ifRange.startsWith("\"") ? ifRange : "";
1313
1311
  const ifRangeDate = ifRangeTag ? "" : ifRange;
1314
1312
  if (ifRange && !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1315
- status(200);
1313
+ response.status = 200;
1316
1314
  size = fileStats.size;
1317
1315
  range = "";
1318
1316
  } else {
1319
- setHeader("content-range", `bytes ${start}-${end}/${fileStats.size}`);
1320
- status(206);
1317
+ response.setHeader("content-range", `bytes ${start}-${end}/${fileStats.size}`);
1318
+ response.status = 206;
1321
1319
  }
1322
1320
  }
1323
- setHeader("accept-ranges", "bytes");
1324
- setHeader("content-type", getMimeType(normalizedPath) || "application/octet-stream");
1325
- setHeader("content-length", size);
1326
- if (options.headers) for (const header of Object.keys(options.headers)) setHeader(header, options.headers[header]);
1321
+ response.setHeader("accept-ranges", "bytes");
1322
+ response.setHeader("content-type", getMimeType(normalizedPath) || "application/octet-stream");
1323
+ response.setHeader("content-length", size);
1324
+ if (options.headers) for (const header of Object.keys(options.headers)) response.setHeader(header, options.headers[header]);
1327
1325
  return method === "HEAD" ? "" : (0, fs.createReadStream)(normalizedPath, range ? {
1328
1326
  start,
1329
1327
  end
@@ -1340,7 +1338,7 @@ function isNotModified(etag, lastModified, clientEtag, clientLM) {
1340
1338
  return false;
1341
1339
  }
1342
1340
  async function listDirectory(dirPath) {
1343
- const { setContentType } = (0, __wooksjs_event_http.useSetHeaders)();
1341
+ const response = (0, __wooksjs_event_http.useResponse)();
1344
1342
  const { url } = (0, __wooksjs_event_http.useRequest)();
1345
1343
  const list = await readdir(dirPath);
1346
1344
  const promises = [];
@@ -1363,7 +1361,7 @@ async function listDirectory(dirPath) {
1363
1361
  name: "..",
1364
1362
  dir: true
1365
1363
  });
1366
- setContentType("text/html");
1364
+ response.setContentType("text/html");
1367
1365
  const styles = "<style type=\"text/css\">\nhtml { font-family: monospace }\nspan { padding: 0px 2px }\n.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n.icon { width: 20px; display: inline-block; text-align: center; }\n.name { width: 250px; display: inline-block; }\n.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n\n</style>";
1368
1366
  return `<html><head><title>Dir</title> ${styles} </head><body><ul>${detailedList.map((d) => `<li> <span class="icon">${d.dir ? "&#128193;" : "&#128462;"}</span><a href="${escapeHtml(path.default.join(url || "", d.name))}"><span class="name text">${escapeHtml(d.name)}</span></a><span class="size text">${d.size && (d.size > 1e4 ? `${Math.round(d.size / 1024 / 1024).toString()}Mb` : `${Math.round(d.size / 1024).toString()}Kb`) || ""}</span><span class="date text">${d.mtime && d.mtime.toISOString() || ""}</li>`).join("\n")}</ul></body></html>`;
1369
1367
  }
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseHttpResponse, HttpError, useHeaders, useRequest, useResponse, useSetCacheControl, useSetHeaders } from "@wooksjs/event-http";
1
+ import { HttpError, useHeaders, useRequest, useResponse } from "@wooksjs/event-http";
2
2
  import { createReadStream, promises } from "fs";
3
3
  import path from "path";
4
4
 
@@ -1224,11 +1224,9 @@ const { stat, readdir } = promises;
1224
1224
  * @returns A readable stream, string body, or HTML directory listing.
1225
1225
  */
1226
1226
  async function serveFile(filePath, options = {}) {
1227
- const { status } = useResponse();
1228
- const { setHeader, removeHeader } = useSetHeaders();
1227
+ const response = useResponse();
1229
1228
  const headers = useHeaders();
1230
1229
  const { method, url } = useRequest();
1231
- const { setCacheControl, setExpires, setPragmaNoCache } = useSetCacheControl();
1232
1230
  const normalizedPath = normalizePath(filePath, options.baseDir);
1233
1231
  if (!options.allowDotDot) {
1234
1232
  const resolvedBase = path.resolve(options.baseDir || process.cwd());
@@ -1244,7 +1242,7 @@ async function serveFile(filePath, options = {}) {
1244
1242
  }
1245
1243
  throw new HttpError(404);
1246
1244
  }
1247
- status(200);
1245
+ response.status = 200;
1248
1246
  const etag = `"${[
1249
1247
  fileStats.ino,
1250
1248
  fileStats.size,
@@ -1252,25 +1250,25 @@ async function serveFile(filePath, options = {}) {
1252
1250
  ].join("-")}"`;
1253
1251
  const lastModified = new Date(fileStats.mtime);
1254
1252
  if (isNotModified(etag, lastModified, headers["if-none-match"] || "", headers["if-modified-since"] || "")) {
1255
- status(304);
1253
+ response.status = 304;
1256
1254
  return "";
1257
1255
  }
1258
- setHeader("etag", etag);
1259
- setHeader("last-modified", lastModified.toUTCString());
1260
- if (options.cacheControl !== void 0) setCacheControl(options.cacheControl);
1261
- if (options.expires) setExpires(options.expires);
1262
- if (options.pragmaNoCache) setPragmaNoCache(options.pragmaNoCache);
1256
+ response.setHeader("etag", etag);
1257
+ response.setHeader("last-modified", lastModified.toUTCString());
1258
+ if (options.cacheControl !== void 0) response.setCacheControl(options.cacheControl);
1259
+ if (options.expires) response.setExpires(options.expires);
1260
+ if (options.pragmaNoCache) response.setPragmaNoCache(options.pragmaNoCache);
1263
1261
  if (fileStats.isDirectory()) {
1264
1262
  if (options.listDirectory) return listDirectory(normalizedPath);
1265
1263
  else if (options.index) {
1266
- if (!filePath.endsWith("/") && url && !url.endsWith("/")) return new BaseHttpResponse().setStatus(302).setHeader("location", `${url}/`);
1264
+ if (!filePath.endsWith("/") && url && !url.endsWith("/")) return response.setStatus(302).setHeader("location", `${url}/`);
1267
1265
  return serveFile(path.join(filePath, options.index), {
1268
1266
  ...options,
1269
1267
  index: ""
1270
1268
  });
1271
1269
  }
1272
- removeHeader("etag");
1273
- removeHeader("last-modified");
1270
+ response.removeHeader("etag");
1271
+ response.removeHeader("last-modified");
1274
1272
  throw new HttpError(404);
1275
1273
  }
1276
1274
  let range = headers.range;
@@ -1289,18 +1287,18 @@ async function serveFile(filePath, options = {}) {
1289
1287
  const ifRangeTag = ifRange.startsWith("\"") ? ifRange : "";
1290
1288
  const ifRangeDate = ifRangeTag ? "" : ifRange;
1291
1289
  if (ifRange && !isNotModified(etag, lastModified, ifRangeTag, ifRangeDate)) {
1292
- status(200);
1290
+ response.status = 200;
1293
1291
  size = fileStats.size;
1294
1292
  range = "";
1295
1293
  } else {
1296
- setHeader("content-range", `bytes ${start}-${end}/${fileStats.size}`);
1297
- status(206);
1294
+ response.setHeader("content-range", `bytes ${start}-${end}/${fileStats.size}`);
1295
+ response.status = 206;
1298
1296
  }
1299
1297
  }
1300
- setHeader("accept-ranges", "bytes");
1301
- setHeader("content-type", getMimeType(normalizedPath) || "application/octet-stream");
1302
- setHeader("content-length", size);
1303
- if (options.headers) for (const header of Object.keys(options.headers)) setHeader(header, options.headers[header]);
1298
+ response.setHeader("accept-ranges", "bytes");
1299
+ response.setHeader("content-type", getMimeType(normalizedPath) || "application/octet-stream");
1300
+ response.setHeader("content-length", size);
1301
+ if (options.headers) for (const header of Object.keys(options.headers)) response.setHeader(header, options.headers[header]);
1304
1302
  return method === "HEAD" ? "" : createReadStream(normalizedPath, range ? {
1305
1303
  start,
1306
1304
  end
@@ -1317,7 +1315,7 @@ function isNotModified(etag, lastModified, clientEtag, clientLM) {
1317
1315
  return false;
1318
1316
  }
1319
1317
  async function listDirectory(dirPath) {
1320
- const { setContentType } = useSetHeaders();
1318
+ const response = useResponse();
1321
1319
  const { url } = useRequest();
1322
1320
  const list = await readdir(dirPath);
1323
1321
  const promises$1 = [];
@@ -1340,7 +1338,7 @@ async function listDirectory(dirPath) {
1340
1338
  name: "..",
1341
1339
  dir: true
1342
1340
  });
1343
- setContentType("text/html");
1341
+ response.setContentType("text/html");
1344
1342
  const styles = "<style type=\"text/css\">\nhtml { font-family: monospace }\nspan { padding: 0px 2px }\n.text { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n.icon { width: 20px; display: inline-block; text-align: center; }\n.name { width: 250px; display: inline-block; }\n.size { width: 80px; display: inline-block; color: grey; text-align: right; }\n.date { width: 200px; display: inline-block; color: grey; text-align: right; }\n\n</style>";
1345
1343
  return `<html><head><title>Dir</title> ${styles} </head><body><ul>${detailedList.map((d) => `<li> <span class="icon">${d.dir ? "&#128193;" : "&#128462;"}</span><a href="${escapeHtml(path.join(url || "", d.name))}"><span class="name text">${escapeHtml(d.name)}</span></a><span class="size text">${d.size && (d.size > 1e4 ? `${Math.round(d.size / 1024 / 1024).toString()}Mb` : `${Math.round(d.size / 1024).toString()}Kb`) || ""}</span><span class="date text">${d.mtime && d.mtime.toISOString() || ""}</li>`).join("\n")}</ul></body></html>`;
1346
1344
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooksjs/http-static",
3
- "version": "0.6.5",
3
+ "version": "0.7.0",
4
4
  "description": "@wooksjs/http-static",
5
5
  "keywords": [
6
6
  "api",
@@ -42,10 +42,10 @@
42
42
  "devDependencies": {
43
43
  "typescript": "^5.9.3",
44
44
  "vitest": "^3.2.4",
45
- "@wooksjs/event-http": "^0.6.5"
45
+ "@wooksjs/event-http": "^0.7.0"
46
46
  },
47
47
  "peerDependencies": {
48
- "@wooksjs/event-http": "^0.6.5"
48
+ "@wooksjs/event-http": "^0.7.0"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "rolldown -c ../../rolldown.config.mjs"