@proteinjs/db-file 1.6.0 → 1.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/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.7.0](https://github.com/proteinjs/db/compare/@proteinjs/db-file@1.6.1...@proteinjs/db-file@1.7.0) (2026-08-26)
7
+
8
+
9
+ ### Features
10
+
11
+ * HTTP Range on the file proxy route — video seek parity with signed-URL serving ([0a95a3f](https://github.com/proteinjs/db/commit/0a95a3f355411b68a20b5cfbc7d7614962b3cd40))
12
+
13
+
14
+
15
+
16
+
17
+ ## [1.6.1](https://github.com/proteinjs/db/compare/@proteinjs/db-file@1.6.0...@proteinjs/db-file@1.6.1) (2026-08-24)
18
+
19
+ **Note:** Version bump only for package @proteinjs/db-file
20
+
21
+
22
+
23
+
24
+
6
25
  # [1.6.0](https://github.com/proteinjs/db/compare/@proteinjs/db-file@1.5.3...@proteinjs/db-file@1.6.0) (2026-08-23)
7
26
 
8
27
 
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The proxy route's Range arithmetic (RFC 7233 single byte-range), separated pure so the
3
+ * route's seek behavior is hermetically testable:
4
+ *
5
+ * - `undefined` — no (usable) range was asked: serve the whole body as 200. Covers a missing
6
+ * header, a non-`bytes` unit, malformed specs, and multi-range requests (a `<video>` never
7
+ * sends them; the RFC lets a server ignore an unusable Range header entirely).
8
+ * - `'unsatisfiable'` — a syntactically-valid range that selects nothing (start past EOF, an
9
+ * empty suffix, or any range of a zero-byte body): respond 416 with a `Content-Range` naming the size (`bytes * / size`).
10
+ * - `{ start, end }` — serve 206 with `bytes start-end/size` and exactly those bytes
11
+ * (inclusive). An over-long end clamps to the last byte.
12
+ */
13
+ export declare function resolveByteRange(rangeHeader: string | undefined, size: number): {
14
+ start: number;
15
+ end: number;
16
+ } | 'unsatisfiable' | undefined;
17
+ //# sourceMappingURL=byteRange.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byteRange.d.ts","sourceRoot":"","sources":["../../../src/routes/byteRange.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,GAAG,SAAS,CAiC9D"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveByteRange = void 0;
4
+ /**
5
+ * The proxy route's Range arithmetic (RFC 7233 single byte-range), separated pure so the
6
+ * route's seek behavior is hermetically testable:
7
+ *
8
+ * - `undefined` — no (usable) range was asked: serve the whole body as 200. Covers a missing
9
+ * header, a non-`bytes` unit, malformed specs, and multi-range requests (a `<video>` never
10
+ * sends them; the RFC lets a server ignore an unusable Range header entirely).
11
+ * - `'unsatisfiable'` — a syntactically-valid range that selects nothing (start past EOF, an
12
+ * empty suffix, or any range of a zero-byte body): respond 416 with a `Content-Range` naming the size (`bytes * / size`).
13
+ * - `{ start, end }` — serve 206 with `bytes start-end/size` and exactly those bytes
14
+ * (inclusive). An over-long end clamps to the last byte.
15
+ */
16
+ function resolveByteRange(rangeHeader, size) {
17
+ if (!rangeHeader || !rangeHeader.startsWith('bytes=')) {
18
+ return undefined;
19
+ }
20
+ var spec = rangeHeader.slice('bytes='.length).trim();
21
+ if (spec.includes(',')) {
22
+ return undefined; // multi-range — serve whole (no player sends these)
23
+ }
24
+ var match = /^(\d*)-(\d*)$/.exec(spec);
25
+ if (!match || (match[1] === '' && match[2] === '')) {
26
+ return undefined; // malformed — ignore the header per the RFC
27
+ }
28
+ var fromText = match[1], toText = match[2];
29
+ if (fromText === '') {
30
+ // Suffix range: the last N bytes.
31
+ var suffix = Number(toText);
32
+ if (suffix === 0 || size === 0) {
33
+ return 'unsatisfiable';
34
+ }
35
+ return { start: Math.max(0, size - suffix), end: size - 1 };
36
+ }
37
+ var start = Number(fromText);
38
+ if (start >= size) {
39
+ return 'unsatisfiable';
40
+ }
41
+ if (toText === '') {
42
+ return { start: start, end: size - 1 };
43
+ }
44
+ var end = Number(toText);
45
+ if (end < start) {
46
+ return undefined; // malformed (last < first) — ignore the header per the RFC
47
+ }
48
+ return { start: start, end: Math.min(end, size - 1) };
49
+ }
50
+ exports.resolveByteRange = resolveByteRange;
51
+ //# sourceMappingURL=byteRange.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byteRange.js","sourceRoot":"","sources":["../../../src/routes/byteRange.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;GAWG;AACH,SAAgB,gBAAgB,CAC9B,WAA+B,EAC/B,IAAY;IAEZ,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACrD,OAAO,SAAS,CAAC;KAClB;IACD,IAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QACtB,OAAO,SAAS,CAAC,CAAC,oDAAoD;KACvE;IACD,IAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE;QAClD,OAAO,SAAS,CAAC,CAAC,4CAA4C;KAC/D;IACQ,IAAA,QAAQ,GAAY,KAAK,GAAjB,EAAE,MAAM,GAAI,KAAK,GAAT,CAAU;IACnC,IAAI,QAAQ,KAAK,EAAE,EAAE;QACnB,kCAAkC;QAClC,IAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,MAAM,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE;YAC9B,OAAO,eAAe,CAAC;SACxB;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC,EAAE,GAAG,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;KAC7D;IACD,IAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,KAAK,IAAI,IAAI,EAAE;QACjB,OAAO,eAAe,CAAC;KACxB;IACD,IAAI,MAAM,KAAK,EAAE,EAAE;QACjB,OAAO,EAAE,KAAK,OAAA,EAAE,GAAG,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;KACjC;IACD,IAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,GAAG,GAAG,KAAK,EAAE;QACf,OAAO,SAAS,CAAC,CAAC,2DAA2D;KAC9E;IACD,OAAO,EAAE,KAAK,OAAA,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;AACjD,CAAC;AApCD,4CAoCC"}
@@ -8,7 +8,9 @@ import { Route } from '@proteinjs/server-api';
8
8
  * seeking, real caching, bytes never transit the app server. The redirect itself is cached
9
9
  * briefly (client-private, well under the signed TTL) so repeated loads reuse one URL.
10
10
  * - **Proxy** for drivers with no external URL space (`DbFileStorageDriver`): the interface's
11
- * base64 is decoded so every mime — binary or text — serves its true bytes.
11
+ * base64 is decoded so every mime — binary or text — serves its true bytes, and HTTP Range
12
+ * is honored (206/416 via {@link resolveByteRange}) so a `<video>` can SEEK against a
13
+ * proxy-served blob — streaming parity with the signed-URL path instead of download-then-watch.
12
14
  */
13
15
  export declare const getFile: Route;
14
16
  //# sourceMappingURL=getFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAI9C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,EAAE,KAoCrB,CAAC"}
1
+ {"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAK9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,OAAO,EAAE,KAiDrB,CAAC"}
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.getFile = void 0;
40
40
  var FileStorage_1 = require("../FileStorage");
41
41
  var user_1 = require("@proteinjs/user");
42
+ var byteRange_1 = require("./byteRange");
42
43
  /**
43
44
  * Serves a file's bytes. Auth first (logged-in + scoped row read), then one of two paths:
44
45
  *
@@ -48,15 +49,18 @@ var user_1 = require("@proteinjs/user");
48
49
  * seeking, real caching, bytes never transit the app server. The redirect itself is cached
49
50
  * briefly (client-private, well under the signed TTL) so repeated loads reuse one URL.
50
51
  * - **Proxy** for drivers with no external URL space (`DbFileStorageDriver`): the interface's
51
- * base64 is decoded so every mime — binary or text — serves its true bytes.
52
+ * base64 is decoded so every mime — binary or text — serves its true bytes, and HTTP Range
53
+ * is honored (206/416 via {@link resolveByteRange}) so a `<video>` can SEEK against a
54
+ * proxy-served blob — streaming parity with the signed-URL path instead of download-then-watch.
52
55
  */
53
56
  exports.getFile = {
54
57
  path: '/file/:id',
55
58
  method: 'get',
56
59
  onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
57
- var fileId, fileStorage, file, signedUrl, fileDataBase64, safeFilename, error_1;
58
- return __generator(this, function (_a) {
59
- switch (_a.label) {
60
+ var fileId, fileStorage, file, signedUrl, fileDataBase64, bytes, safeFilename, range, error_1;
61
+ var _a;
62
+ return __generator(this, function (_b) {
63
+ switch (_b.label) {
60
64
  case 0:
61
65
  if (!user_1.UserAuth.isLoggedIn()) {
62
66
  response.status(401).send('User not logged in');
@@ -64,19 +68,19 @@ exports.getFile = {
64
68
  }
65
69
  fileId = request.params.id;
66
70
  fileStorage = (0, FileStorage_1.getFileStorage)();
67
- _a.label = 1;
71
+ _b.label = 1;
68
72
  case 1:
69
- _a.trys.push([1, 5, , 6]);
73
+ _b.trys.push([1, 5, , 6]);
70
74
  return [4 /*yield*/, fileStorage.getFile(fileId)];
71
75
  case 2:
72
- file = _a.sent();
76
+ file = _b.sent();
73
77
  if (!file) {
74
78
  response.status(404).send('File not found');
75
79
  return [2 /*return*/];
76
80
  }
77
81
  return [4 /*yield*/, fileStorage.getSignedUrl(fileId)];
78
82
  case 3:
79
- signedUrl = _a.sent();
83
+ signedUrl = _b.sent();
80
84
  if (signedUrl) {
81
85
  response.setHeader('Cache-Control', 'private, max-age=300');
82
86
  response.redirect(302, signedUrl);
@@ -84,14 +88,27 @@ exports.getFile = {
84
88
  }
85
89
  return [4 /*yield*/, fileStorage.getFileData(fileId)];
86
90
  case 4:
87
- fileDataBase64 = _a.sent();
91
+ fileDataBase64 = _b.sent();
92
+ bytes = Buffer.from(fileDataBase64, 'base64');
88
93
  safeFilename = encodeURIComponent(file.name);
89
94
  response.setHeader('Content-Disposition', "inline; filename=\"".concat(safeFilename, "\""));
90
95
  response.setHeader('Content-Type', file.type);
91
- response.send(Buffer.from(fileDataBase64, 'base64'));
96
+ response.setHeader('Accept-Ranges', 'bytes');
97
+ range = (0, byteRange_1.resolveByteRange)((_a = request.headers) === null || _a === void 0 ? void 0 : _a.range, bytes.length);
98
+ if (range === 'unsatisfiable') {
99
+ response.setHeader('Content-Range', "bytes */".concat(bytes.length));
100
+ response.status(416).send('Range Not Satisfiable');
101
+ return [2 /*return*/];
102
+ }
103
+ if (range) {
104
+ response.setHeader('Content-Range', "bytes ".concat(range.start, "-").concat(range.end, "/").concat(bytes.length));
105
+ response.status(206).send(bytes.subarray(range.start, range.end + 1));
106
+ return [2 /*return*/];
107
+ }
108
+ response.send(bytes);
92
109
  return [3 /*break*/, 6];
93
110
  case 5:
94
- error_1 = _a.sent();
111
+ error_1 = _b.sent();
95
112
  console.error("Error fetching file (".concat(fileId, "):"), error_1);
96
113
  response.status(500).send('Internal Server Error');
97
114
  return [3 /*break*/, 6];
@@ -1 +1 @@
1
- {"version":3,"file":"getFile.js","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8CAAgD;AAChD,wCAA2C;AAE3C;;;;;;;;;;GAUG;AACU,QAAA,OAAO,GAAU;IAC5B,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,KAAK;IACb,SAAS,EAAE,UAAO,OAAO,EAAE,QAAQ;;;;;oBACjC,IAAI,CAAC,eAAQ,CAAC,UAAU,EAAE,EAAE;wBAC1B,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;wBAChD,sBAAO;qBACR;oBAEK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3B,WAAW,GAAG,IAAA,4BAAc,GAAE,CAAC;;;;oBAGtB,qBAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAA;;oBAAxC,IAAI,GAAG,SAAiC;oBAC9C,IAAI,CAAC,IAAI,EAAE;wBACT,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBAC5C,sBAAO;qBACR;oBAEiB,qBAAM,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC,EAAA;;oBAAlD,SAAS,GAAG,SAAsC;oBACxD,IAAI,SAAS,EAAE;wBACb,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;wBAC5D,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;wBAClC,sBAAO;qBACR;oBAEsB,qBAAM,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,EAAA;;oBAAtD,cAAc,GAAG,SAAqC;oBACtD,YAAY,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACnD,QAAQ,CAAC,SAAS,CAAC,qBAAqB,EAAE,6BAAqB,YAAY,OAAG,CAAC,CAAC;oBAChF,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC9C,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;;;;oBAErD,OAAO,CAAC,KAAK,CAAC,+BAAwB,MAAM,OAAI,EAAE,OAAK,CAAC,CAAC;oBACzD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;;;;;SAEtD;CACF,CAAC"}
1
+ {"version":3,"file":"getFile.js","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8CAAgD;AAChD,wCAA2C;AAC3C,yCAA+C;AAE/C;;;;;;;;;;;;GAYG;AACU,QAAA,OAAO,GAAU;IAC5B,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,KAAK;IACb,SAAS,EAAE,UAAO,OAAO,EAAE,QAAQ;;;;;;oBACjC,IAAI,CAAC,eAAQ,CAAC,UAAU,EAAE,EAAE;wBAC1B,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;wBAChD,sBAAO;qBACR;oBAEK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3B,WAAW,GAAG,IAAA,4BAAc,GAAE,CAAC;;;;oBAGtB,qBAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAA;;oBAAxC,IAAI,GAAG,SAAiC;oBAC9C,IAAI,CAAC,IAAI,EAAE;wBACT,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBAC5C,sBAAO;qBACR;oBAEiB,qBAAM,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC,EAAA;;oBAAlD,SAAS,GAAG,SAAsC;oBACxD,IAAI,SAAS,EAAE;wBACb,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;wBAC5D,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;wBAClC,sBAAO;qBACR;oBAEsB,qBAAM,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,EAAA;;oBAAtD,cAAc,GAAG,SAAqC;oBACtD,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;oBAC9C,YAAY,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACnD,QAAQ,CAAC,SAAS,CAAC,qBAAqB,EAAE,6BAAqB,YAAY,OAAG,CAAC,CAAC;oBAChF,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC9C,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;oBACvC,KAAK,GAAG,IAAA,4BAAgB,EAAC,MAAA,OAAO,CAAC,OAAO,0CAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;oBACrE,IAAI,KAAK,KAAK,eAAe,EAAE;wBAC7B,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,kBAAW,KAAK,CAAC,MAAM,CAAE,CAAC,CAAC;wBAC/D,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;wBACnD,sBAAO;qBACR;oBACD,IAAI,KAAK,EAAE;wBACT,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,gBAAS,KAAK,CAAC,KAAK,cAAI,KAAK,CAAC,GAAG,cAAI,KAAK,CAAC,MAAM,CAAE,CAAC,CAAC;wBACzF,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;wBACtE,sBAAO;qBACR;oBACD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;;;oBAErB,OAAO,CAAC,KAAK,CAAC,+BAAwB,MAAM,OAAI,EAAE,OAAK,CAAC,CAAC;oBACzD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;;;;;SAEtD;CACF,CAAC"}
@@ -241,19 +241,22 @@ var ResponseRecorder = /** @class */ (function () {
241
241
  };
242
242
  return ResponseRecorder;
243
243
  }());
244
- var invokeRoute = function (fileId) { return __awaiter(void 0, void 0, void 0, function () {
245
- var response;
246
- return __generator(this, function (_a) {
247
- switch (_a.label) {
248
- case 0:
249
- response = new ResponseRecorder();
250
- return [4 /*yield*/, getFile_1.getFile.onRequest({ params: { id: fileId } }, response)];
251
- case 1:
252
- _a.sent();
253
- return [2 /*return*/, response];
254
- }
244
+ var invokeRoute = function (fileId, headers) {
245
+ if (headers === void 0) { headers = {}; }
246
+ return __awaiter(void 0, void 0, void 0, function () {
247
+ var response;
248
+ return __generator(this, function (_a) {
249
+ switch (_a.label) {
250
+ case 0:
251
+ response = new ResponseRecorder();
252
+ return [4 /*yield*/, getFile_1.getFile.onRequest({ params: { id: fileId }, headers: headers }, response)];
253
+ case 1:
254
+ _a.sent();
255
+ return [2 /*return*/, response];
256
+ }
257
+ });
255
258
  });
256
- }); };
259
+ };
257
260
  var testEnv = new FileTestEnvironment_1.FileTestEnvironment();
258
261
  var user;
259
262
  beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
@@ -325,6 +328,129 @@ describe('GET /file/:id — proxy serving (driver without signed URLs)', functio
325
328
  }
326
329
  });
327
330
  }); });
331
+ // ── HTTP Range on the proxy path — video streaming parity with the signed-URL path, so a
332
+ // <video> can seek against a proxy-served blob instead of downloading it whole. ──────────
333
+ describe('Range requests (video seek parity with the signed-URL path)', function () {
334
+ var videoFileId;
335
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
336
+ var file;
337
+ return __generator(this, function (_a) {
338
+ switch (_a.label) {
339
+ case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: 'smoke.webm', type: 'video/webm', size: rawBytes.length }, rawBytes.toString('base64'))];
340
+ case 1:
341
+ file = _a.sent();
342
+ videoFileId = file.id;
343
+ return [2 /*return*/];
344
+ }
345
+ });
346
+ }); });
347
+ it('advertises Accept-Ranges and serves the whole file when no Range is asked', function () { return __awaiter(void 0, void 0, void 0, function () {
348
+ var response;
349
+ return __generator(this, function (_a) {
350
+ switch (_a.label) {
351
+ case 0: return [4 /*yield*/, invokeRoute(videoFileId)];
352
+ case 1:
353
+ response = _a.sent();
354
+ expect(response.headers['accept-ranges']).toEqual('bytes');
355
+ expect(Buffer.compare(response.body, rawBytes)).toBe(0);
356
+ return [2 /*return*/];
357
+ }
358
+ });
359
+ }); });
360
+ it('serves a bounded range as 206 with Content-Range and exactly those bytes', function () { return __awaiter(void 0, void 0, void 0, function () {
361
+ var response;
362
+ return __generator(this, function (_a) {
363
+ switch (_a.label) {
364
+ case 0: return [4 /*yield*/, invokeRoute(videoFileId, { range: 'bytes=10-19' })];
365
+ case 1:
366
+ response = _a.sent();
367
+ expect(response.statusCode).toEqual(206);
368
+ expect(response.headers['content-range']).toEqual('bytes 10-19/256');
369
+ expect(response.headers['accept-ranges']).toEqual('bytes');
370
+ expect(Buffer.compare(response.body, rawBytes.subarray(10, 20))).toBe(0);
371
+ return [2 /*return*/];
372
+ }
373
+ });
374
+ }); });
375
+ it('serves an open-ended range to EOF (the video-seek shape)', function () { return __awaiter(void 0, void 0, void 0, function () {
376
+ var response;
377
+ return __generator(this, function (_a) {
378
+ switch (_a.label) {
379
+ case 0: return [4 /*yield*/, invokeRoute(videoFileId, { range: 'bytes=200-' })];
380
+ case 1:
381
+ response = _a.sent();
382
+ expect(response.statusCode).toEqual(206);
383
+ expect(response.headers['content-range']).toEqual('bytes 200-255/256');
384
+ expect(Buffer.compare(response.body, rawBytes.subarray(200))).toBe(0);
385
+ return [2 /*return*/];
386
+ }
387
+ });
388
+ }); });
389
+ it('serves a suffix range (last N bytes)', function () { return __awaiter(void 0, void 0, void 0, function () {
390
+ var response;
391
+ return __generator(this, function (_a) {
392
+ switch (_a.label) {
393
+ case 0: return [4 /*yield*/, invokeRoute(videoFileId, { range: 'bytes=-16' })];
394
+ case 1:
395
+ response = _a.sent();
396
+ expect(response.statusCode).toEqual(206);
397
+ expect(response.headers['content-range']).toEqual('bytes 240-255/256');
398
+ expect(Buffer.compare(response.body, rawBytes.subarray(240))).toBe(0);
399
+ return [2 /*return*/];
400
+ }
401
+ });
402
+ }); });
403
+ it('clamps an over-long range to the end of the file', function () { return __awaiter(void 0, void 0, void 0, function () {
404
+ var response;
405
+ return __generator(this, function (_a) {
406
+ switch (_a.label) {
407
+ case 0: return [4 /*yield*/, invokeRoute(videoFileId, { range: 'bytes=250-999' })];
408
+ case 1:
409
+ response = _a.sent();
410
+ expect(response.statusCode).toEqual(206);
411
+ expect(response.headers['content-range']).toEqual('bytes 250-255/256');
412
+ expect(Buffer.compare(response.body, rawBytes.subarray(250))).toBe(0);
413
+ return [2 /*return*/];
414
+ }
415
+ });
416
+ }); });
417
+ it('416s an unsatisfiable range, naming the size', function () { return __awaiter(void 0, void 0, void 0, function () {
418
+ var response;
419
+ return __generator(this, function (_a) {
420
+ switch (_a.label) {
421
+ case 0: return [4 /*yield*/, invokeRoute(videoFileId, { range: 'bytes=999-' })];
422
+ case 1:
423
+ response = _a.sent();
424
+ expect(response.statusCode).toEqual(416);
425
+ expect(response.headers['content-range']).toEqual('bytes */256');
426
+ return [2 /*return*/];
427
+ }
428
+ });
429
+ }); });
430
+ it('ignores malformed and multi-range headers — the whole file serves', function () { return __awaiter(void 0, void 0, void 0, function () {
431
+ var _i, _a, range, response;
432
+ return __generator(this, function (_b) {
433
+ switch (_b.label) {
434
+ case 0:
435
+ _i = 0, _a = ['bytes=5-2', 'apples=1-2', 'bytes=0-1,3-4', 'bytes=x-y'];
436
+ _b.label = 1;
437
+ case 1:
438
+ if (!(_i < _a.length)) return [3 /*break*/, 4];
439
+ range = _a[_i];
440
+ return [4 /*yield*/, invokeRoute(videoFileId, { range: range })];
441
+ case 2:
442
+ response = _b.sent();
443
+ expect(response.statusCode).not.toEqual(206);
444
+ expect(Buffer.compare(response.body, rawBytes)).toBe(0);
445
+ _b.label = 3;
446
+ case 3:
447
+ _i++;
448
+ return [3 /*break*/, 1];
449
+ case 4: return [2 /*return*/];
450
+ }
451
+ });
452
+ }); });
453
+ });
328
454
  });
329
455
  describe('GET /file/:id — signed-URL serving (302 redirect)', function () {
330
456
  var driver = new TestSignedUrlFileStorageDriver();
@@ -1 +1 @@
1
- {"version":3,"file":"GetFileRoute.test.js","sourceRoot":"","sources":["../../test/GetFileRoute.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GetFileRoute.test.js","sourceRoot":"","sources":["../../test/GetFileRoute.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db-file",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "File storage in the db",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,16 +22,16 @@
22
22
  "watch": "reflection-watch"
23
23
  },
24
24
  "dependencies": {
25
- "@proteinjs/db": "^1.34.3",
25
+ "@proteinjs/db": "^1.35.0",
26
26
  "@proteinjs/logger": "^1.0.21",
27
- "@proteinjs/reflection": "^1.1.14",
27
+ "@proteinjs/reflection": "^1.2.0",
28
28
  "@proteinjs/server-api": "^3.0.11",
29
29
  "@proteinjs/service": "^1.5.1",
30
30
  "@proteinjs/user": "^1.7.1",
31
31
  "moment": "2.29.4"
32
32
  },
33
33
  "devDependencies": {
34
- "@proteinjs/db-driver-spanner": "^1.23.1",
34
+ "@proteinjs/db-driver-spanner": "^1.23.3",
35
35
  "@proteinjs/reflection-build": "^2.0.1",
36
36
  "@types/jest": "29.5.5",
37
37
  "@types/node": "14.0.27",
@@ -46,5 +46,5 @@
46
46
  },
47
47
  "main": "./dist/generated/index.js",
48
48
  "types": "./dist/generated/index.d.ts",
49
- "gitHead": "56f83ab4032e89ec74e6138fe05a8633c2f281fc"
49
+ "gitHead": "521d5d1ca2f86f0ed2bef0309b0dde30506f132b"
50
50
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The proxy route's Range arithmetic (RFC 7233 single byte-range), separated pure so the
3
+ * route's seek behavior is hermetically testable:
4
+ *
5
+ * - `undefined` — no (usable) range was asked: serve the whole body as 200. Covers a missing
6
+ * header, a non-`bytes` unit, malformed specs, and multi-range requests (a `<video>` never
7
+ * sends them; the RFC lets a server ignore an unusable Range header entirely).
8
+ * - `'unsatisfiable'` — a syntactically-valid range that selects nothing (start past EOF, an
9
+ * empty suffix, or any range of a zero-byte body): respond 416 with a `Content-Range` naming the size (`bytes * / size`).
10
+ * - `{ start, end }` — serve 206 with `bytes start-end/size` and exactly those bytes
11
+ * (inclusive). An over-long end clamps to the last byte.
12
+ */
13
+ export function resolveByteRange(
14
+ rangeHeader: string | undefined,
15
+ size: number
16
+ ): { start: number; end: number } | 'unsatisfiable' | undefined {
17
+ if (!rangeHeader || !rangeHeader.startsWith('bytes=')) {
18
+ return undefined;
19
+ }
20
+ const spec = rangeHeader.slice('bytes='.length).trim();
21
+ if (spec.includes(',')) {
22
+ return undefined; // multi-range — serve whole (no player sends these)
23
+ }
24
+ const match = /^(\d*)-(\d*)$/.exec(spec);
25
+ if (!match || (match[1] === '' && match[2] === '')) {
26
+ return undefined; // malformed — ignore the header per the RFC
27
+ }
28
+ const [, fromText, toText] = match;
29
+ if (fromText === '') {
30
+ // Suffix range: the last N bytes.
31
+ const suffix = Number(toText);
32
+ if (suffix === 0 || size === 0) {
33
+ return 'unsatisfiable';
34
+ }
35
+ return { start: Math.max(0, size - suffix), end: size - 1 };
36
+ }
37
+ const start = Number(fromText);
38
+ if (start >= size) {
39
+ return 'unsatisfiable';
40
+ }
41
+ if (toText === '') {
42
+ return { start, end: size - 1 };
43
+ }
44
+ const end = Number(toText);
45
+ if (end < start) {
46
+ return undefined; // malformed (last < first) — ignore the header per the RFC
47
+ }
48
+ return { start, end: Math.min(end, size - 1) };
49
+ }
@@ -1,6 +1,7 @@
1
1
  import { Route } from '@proteinjs/server-api';
2
2
  import { getFileStorage } from '../FileStorage';
3
3
  import { UserAuth } from '@proteinjs/user';
4
+ import { resolveByteRange } from './byteRange';
4
5
 
5
6
  /**
6
7
  * Serves a file's bytes. Auth first (logged-in + scoped row read), then one of two paths:
@@ -11,7 +12,9 @@ import { UserAuth } from '@proteinjs/user';
11
12
  * seeking, real caching, bytes never transit the app server. The redirect itself is cached
12
13
  * briefly (client-private, well under the signed TTL) so repeated loads reuse one URL.
13
14
  * - **Proxy** for drivers with no external URL space (`DbFileStorageDriver`): the interface's
14
- * base64 is decoded so every mime — binary or text — serves its true bytes.
15
+ * base64 is decoded so every mime — binary or text — serves its true bytes, and HTTP Range
16
+ * is honored (206/416 via {@link resolveByteRange}) so a `<video>` can SEEK against a
17
+ * proxy-served blob — streaming parity with the signed-URL path instead of download-then-watch.
15
18
  */
16
19
  export const getFile: Route = {
17
20
  path: '/file/:id',
@@ -40,10 +43,23 @@ export const getFile: Route = {
40
43
  }
41
44
 
42
45
  const fileDataBase64 = await fileStorage.getFileData(fileId);
46
+ const bytes = Buffer.from(fileDataBase64, 'base64');
43
47
  const safeFilename = encodeURIComponent(file.name);
44
48
  response.setHeader('Content-Disposition', `inline; filename="${safeFilename}"`);
45
49
  response.setHeader('Content-Type', file.type);
46
- response.send(Buffer.from(fileDataBase64, 'base64'));
50
+ response.setHeader('Accept-Ranges', 'bytes');
51
+ const range = resolveByteRange(request.headers?.range, bytes.length);
52
+ if (range === 'unsatisfiable') {
53
+ response.setHeader('Content-Range', `bytes */${bytes.length}`);
54
+ response.status(416).send('Range Not Satisfiable');
55
+ return;
56
+ }
57
+ if (range) {
58
+ response.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${bytes.length}`);
59
+ response.status(206).send(bytes.subarray(range.start, range.end + 1));
60
+ return;
61
+ }
62
+ response.send(bytes);
47
63
  } catch (error) {
48
64
  console.error(`Error fetching file (${fileId}):`, error);
49
65
  response.status(500).send('Internal Server Error');
@@ -131,9 +131,12 @@ class ResponseRecorder {
131
131
  type RouteRequest = Parameters<typeof getFile.onRequest>[0];
132
132
  type RouteResponse = Parameters<typeof getFile.onRequest>[1];
133
133
 
134
- const invokeRoute = async (fileId: string): Promise<ResponseRecorder> => {
134
+ const invokeRoute = async (fileId: string, headers: Record<string, string> = {}): Promise<ResponseRecorder> => {
135
135
  const response = new ResponseRecorder();
136
- await getFile.onRequest({ params: { id: fileId } } as unknown as RouteRequest, response as unknown as RouteResponse);
136
+ await getFile.onRequest(
137
+ { params: { id: fileId }, headers } as unknown as RouteRequest,
138
+ response as unknown as RouteResponse
139
+ );
137
140
  return response;
138
141
  };
139
142
 
@@ -189,6 +192,69 @@ describe('GET /file/:id — proxy serving (driver without signed URLs)', () => {
189
192
  expect(Buffer.isBuffer(response.body)).toBe(true);
190
193
  expect(Buffer.compare(response.body as Buffer, rawBytes)).toBe(0);
191
194
  });
195
+
196
+ // ── HTTP Range on the proxy path — video streaming parity with the signed-URL path, so a
197
+ // <video> can seek against a proxy-served blob instead of downloading it whole. ──────────
198
+ describe('Range requests (video seek parity with the signed-URL path)', () => {
199
+ let videoFileId: string;
200
+
201
+ beforeAll(async () => {
202
+ const file = await new FileStorage().createFile(
203
+ { name: 'smoke.webm', type: 'video/webm', size: rawBytes.length } as File,
204
+ rawBytes.toString('base64')
205
+ );
206
+ videoFileId = file.id;
207
+ });
208
+
209
+ it('advertises Accept-Ranges and serves the whole file when no Range is asked', async () => {
210
+ const response = await invokeRoute(videoFileId);
211
+ expect(response.headers['accept-ranges']).toEqual('bytes');
212
+ expect(Buffer.compare(response.body as Buffer, rawBytes)).toBe(0);
213
+ });
214
+
215
+ it('serves a bounded range as 206 with Content-Range and exactly those bytes', async () => {
216
+ const response = await invokeRoute(videoFileId, { range: 'bytes=10-19' });
217
+ expect(response.statusCode).toEqual(206);
218
+ expect(response.headers['content-range']).toEqual('bytes 10-19/256');
219
+ expect(response.headers['accept-ranges']).toEqual('bytes');
220
+ expect(Buffer.compare(response.body as Buffer, rawBytes.subarray(10, 20))).toBe(0);
221
+ });
222
+
223
+ it('serves an open-ended range to EOF (the video-seek shape)', async () => {
224
+ const response = await invokeRoute(videoFileId, { range: 'bytes=200-' });
225
+ expect(response.statusCode).toEqual(206);
226
+ expect(response.headers['content-range']).toEqual('bytes 200-255/256');
227
+ expect(Buffer.compare(response.body as Buffer, rawBytes.subarray(200))).toBe(0);
228
+ });
229
+
230
+ it('serves a suffix range (last N bytes)', async () => {
231
+ const response = await invokeRoute(videoFileId, { range: 'bytes=-16' });
232
+ expect(response.statusCode).toEqual(206);
233
+ expect(response.headers['content-range']).toEqual('bytes 240-255/256');
234
+ expect(Buffer.compare(response.body as Buffer, rawBytes.subarray(240))).toBe(0);
235
+ });
236
+
237
+ it('clamps an over-long range to the end of the file', async () => {
238
+ const response = await invokeRoute(videoFileId, { range: 'bytes=250-999' });
239
+ expect(response.statusCode).toEqual(206);
240
+ expect(response.headers['content-range']).toEqual('bytes 250-255/256');
241
+ expect(Buffer.compare(response.body as Buffer, rawBytes.subarray(250))).toBe(0);
242
+ });
243
+
244
+ it('416s an unsatisfiable range, naming the size', async () => {
245
+ const response = await invokeRoute(videoFileId, { range: 'bytes=999-' });
246
+ expect(response.statusCode).toEqual(416);
247
+ expect(response.headers['content-range']).toEqual('bytes */256');
248
+ });
249
+
250
+ it('ignores malformed and multi-range headers — the whole file serves', async () => {
251
+ for (const range of ['bytes=5-2', 'apples=1-2', 'bytes=0-1,3-4', 'bytes=x-y']) {
252
+ const response = await invokeRoute(videoFileId, { range });
253
+ expect(response.statusCode).not.toEqual(206);
254
+ expect(Buffer.compare(response.body as Buffer, rawBytes)).toBe(0);
255
+ }
256
+ });
257
+ });
192
258
  });
193
259
 
194
260
  describe('GET /file/:id — signed-URL serving (302 redirect)', () => {