@proteinjs/db-file 1.8.3 → 1.9.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.
Files changed (97) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/generated/index.d.ts.map +1 -1
  3. package/dist/generated/index.js +3 -1
  4. package/dist/generated/index.js.map +1 -1
  5. package/dist/index.d.ts +4 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/src/DbFileStorageDriver.d.ts +7 -0
  10. package/dist/src/DbFileStorageDriver.d.ts.map +1 -1
  11. package/dist/src/DbFileStorageDriver.js +69 -41
  12. package/dist/src/DbFileStorageDriver.js.map +1 -1
  13. package/dist/src/FileCopyForOthers.d.ts +52 -0
  14. package/dist/src/FileCopyForOthers.d.ts.map +1 -0
  15. package/dist/src/FileCopyForOthers.js +62 -0
  16. package/dist/src/FileCopyForOthers.js.map +1 -0
  17. package/dist/src/FileStorage.d.ts +145 -11
  18. package/dist/src/FileStorage.d.ts.map +1 -1
  19. package/dist/src/FileStorage.js +537 -41
  20. package/dist/src/FileStorage.js.map +1 -1
  21. package/dist/src/FileStorageDriver.d.ts +11 -0
  22. package/dist/src/FileStorageDriver.d.ts.map +1 -1
  23. package/dist/src/FileStorageError.d.ts +62 -0
  24. package/dist/src/FileStorageError.d.ts.map +1 -0
  25. package/dist/src/FileStorageError.js +118 -0
  26. package/dist/src/FileStorageError.js.map +1 -0
  27. package/dist/src/FileVariantMaker.d.ts +61 -0
  28. package/dist/src/FileVariantMaker.d.ts.map +1 -0
  29. package/dist/src/FileVariantMaker.js +62 -0
  30. package/dist/src/FileVariantMaker.js.map +1 -0
  31. package/dist/src/routes/FileResponder.d.ts +41 -0
  32. package/dist/src/routes/FileResponder.d.ts.map +1 -0
  33. package/dist/src/routes/FileResponder.js +135 -0
  34. package/dist/src/routes/FileResponder.js.map +1 -0
  35. package/dist/src/routes/getFile.d.ts +5 -13
  36. package/dist/src/routes/getFile.d.ts.map +1 -1
  37. package/dist/src/routes/getFile.js +20 -56
  38. package/dist/src/routes/getFile.js.map +1 -1
  39. package/dist/src/routes/getFileVariant.d.ts +20 -0
  40. package/dist/src/routes/getFileVariant.d.ts.map +1 -0
  41. package/dist/src/routes/getFileVariant.js +112 -0
  42. package/dist/src/routes/getFileVariant.js.map +1 -0
  43. package/dist/src/routes/getFileVariantRoute.d.ts +7 -0
  44. package/dist/src/routes/getFileVariantRoute.d.ts.map +1 -0
  45. package/dist/src/routes/getFileVariantRoute.js +9 -0
  46. package/dist/src/routes/getFileVariantRoute.js.map +1 -0
  47. package/dist/src/tables/FileTable.d.ts +55 -2
  48. package/dist/src/tables/FileTable.d.ts.map +1 -1
  49. package/dist/src/tables/FileTable.js +8 -0
  50. package/dist/src/tables/FileTable.js.map +1 -1
  51. package/dist/test/DbFileStorageDriverMissingBytes.test.d.ts +2 -0
  52. package/dist/test/DbFileStorageDriverMissingBytes.test.d.ts.map +1 -0
  53. package/dist/test/DbFileStorageDriverMissingBytes.test.js +184 -0
  54. package/dist/test/DbFileStorageDriverMissingBytes.test.js.map +1 -0
  55. package/dist/test/FileByteAccess.test.js +6 -3
  56. package/dist/test/FileByteAccess.test.js.map +1 -1
  57. package/dist/test/FileCopyForOthers.test.d.ts +2 -0
  58. package/dist/test/FileCopyForOthers.test.d.ts.map +1 -0
  59. package/dist/test/FileCopyForOthers.test.js +1143 -0
  60. package/dist/test/FileCopyForOthers.test.js.map +1 -0
  61. package/dist/test/FileMediaMetadata.test.js +60 -5
  62. package/dist/test/FileMediaMetadata.test.js.map +1 -1
  63. package/dist/test/FileStorageError.test.d.ts +2 -0
  64. package/dist/test/FileStorageError.test.d.ts.map +1 -0
  65. package/dist/test/FileStorageError.test.js +68 -0
  66. package/dist/test/FileStorageError.test.js.map +1 -0
  67. package/dist/test/FileTestEnvironment.d.ts.map +1 -1
  68. package/dist/test/FileTestEnvironment.js +3 -0
  69. package/dist/test/FileTestEnvironment.js.map +1 -1
  70. package/dist/test/FileVariants.test.d.ts +2 -0
  71. package/dist/test/FileVariants.test.d.ts.map +1 -0
  72. package/dist/test/FileVariants.test.js +1008 -0
  73. package/dist/test/FileVariants.test.js.map +1 -0
  74. package/dist/test/GetFileRoute.test.js +29 -0
  75. package/dist/test/GetFileRoute.test.js.map +1 -1
  76. package/generated/index.ts +3 -1
  77. package/index.ts +4 -0
  78. package/package.json +5 -5
  79. package/src/DbFileStorageDriver.ts +32 -21
  80. package/src/FileCopyForOthers.ts +71 -0
  81. package/src/FileStorage.ts +351 -18
  82. package/src/FileStorageDriver.ts +12 -0
  83. package/src/FileStorageError.ts +123 -0
  84. package/src/FileVariantMaker.ts +84 -0
  85. package/src/routes/FileResponder.ts +85 -0
  86. package/src/routes/getFile.ts +9 -44
  87. package/src/routes/getFileVariant.ts +53 -0
  88. package/src/routes/getFileVariantRoute.ts +7 -0
  89. package/src/tables/FileTable.ts +57 -1
  90. package/test/DbFileStorageDriverMissingBytes.test.ts +85 -0
  91. package/test/FileByteAccess.test.ts +7 -1
  92. package/test/FileCopyForOthers.test.ts +702 -0
  93. package/test/FileMediaMetadata.test.ts +44 -0
  94. package/test/FileStorageError.test.ts +79 -0
  95. package/test/FileTestEnvironment.ts +3 -0
  96. package/test/FileVariants.test.ts +604 -0
  97. package/test/GetFileRoute.test.ts +19 -0
@@ -39,83 +39,47 @@ 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
+ var FileResponder_1 = require("./FileResponder");
43
43
  /**
44
44
  * Serves a file's bytes. Auth first: logged-in, then the `FileStorage.getFile` row read as the
45
- * access decision — the caller's SCOPED read, else the shared-content reachability leg
46
- * (`FileReachabilityResolver`: the caller may read a file when they may read a row that
47
- * references it, e.g. a shared thought's media node). Then one of two serving paths:
48
- *
49
- * - **302 redirect** to a short-lived signed URL when the driver's store has an external URL
50
- * space (GCS). The app URL stays the one stable reference every `<img>`/`<video>`/chip uses;
51
- * the client follows the redirect and reads the store directly — native Range/206 for video
52
- * seeking, real caching, bytes never transit the app server. The redirect itself is cached
53
- * briefly (client-private, well under the signed TTL) so repeated loads reuse one URL.
54
- * - **Proxy** for drivers with no external URL space (`DbFileStorageDriver`): the interface's
55
- * base64 is decoded so every mime — binary or text — serves its true bytes, and HTTP Range
56
- * is honored (206/416 via {@link resolveByteRange}) so a `<video>` can SEEK against a
57
- * proxy-served blob — streaming parity with the signed-URL path instead of download-then-watch.
45
+ * access decision — the caller's SCOPED read, else a variant's original, else the shared-content
46
+ * reachability leg (`FileReachabilityResolver`: the caller may read a file when they may read a
47
+ * row that references it, e.g. a shared thought's media node). Then {@link FileResponder} — the
48
+ * one serving path (a 302 to a signed URL, or the proxy with Range) every file route answers
49
+ * through.
58
50
  */
59
51
  exports.getFile = {
60
52
  path: '/file/:id',
61
53
  method: 'get',
62
54
  onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
63
- var fileId, fileStorage, file, signedUrl, fileDataBase64, bytes, safeFilename, range, error_1;
64
- var _a;
65
- return __generator(this, function (_b) {
66
- switch (_b.label) {
55
+ var fileId, file, error_1;
56
+ return __generator(this, function (_a) {
57
+ switch (_a.label) {
67
58
  case 0:
68
59
  if (!user_1.UserAuth.isLoggedIn()) {
69
60
  response.status(401).send('User not logged in');
70
61
  return [2 /*return*/];
71
62
  }
72
63
  fileId = request.params.id;
73
- fileStorage = (0, FileStorage_1.getFileStorage)();
74
- _b.label = 1;
64
+ _a.label = 1;
75
65
  case 1:
76
- _b.trys.push([1, 5, , 6]);
77
- return [4 /*yield*/, fileStorage.getFile(fileId)];
66
+ _a.trys.push([1, 4, , 5]);
67
+ return [4 /*yield*/, (0, FileStorage_1.getFileStorage)().getFile(fileId)];
78
68
  case 2:
79
- file = _b.sent();
69
+ file = _a.sent();
80
70
  if (!file) {
81
71
  response.status(404).send('File not found');
82
72
  return [2 /*return*/];
83
73
  }
84
- return [4 /*yield*/, fileStorage.getSignedUrl(fileId)];
74
+ return [4 /*yield*/, FileResponder_1.FileResponder.serve(file, request, response)];
85
75
  case 3:
86
- signedUrl = _b.sent();
87
- if (signedUrl) {
88
- response.setHeader('Cache-Control', 'private, max-age=300');
89
- response.redirect(302, signedUrl);
90
- return [2 /*return*/];
91
- }
92
- return [4 /*yield*/, fileStorage.getFileData(fileId)];
76
+ _a.sent();
77
+ return [3 /*break*/, 5];
93
78
  case 4:
94
- fileDataBase64 = _b.sent();
95
- bytes = Buffer.from(fileDataBase64, 'base64');
96
- safeFilename = encodeURIComponent(file.name);
97
- response.setHeader('Content-Disposition', "inline; filename=\"".concat(safeFilename, "\""));
98
- response.setHeader('Content-Type', file.type);
99
- response.setHeader('Accept-Ranges', 'bytes');
100
- range = (0, byteRange_1.resolveByteRange)((_a = request.headers) === null || _a === void 0 ? void 0 : _a.range, bytes.length);
101
- if (range === 'unsatisfiable') {
102
- response.setHeader('Content-Range', "bytes */".concat(bytes.length));
103
- response.status(416).send('Range Not Satisfiable');
104
- return [2 /*return*/];
105
- }
106
- if (range) {
107
- response.setHeader('Content-Range', "bytes ".concat(range.start, "-").concat(range.end, "/").concat(bytes.length));
108
- response.status(206).send(bytes.subarray(range.start, range.end + 1));
109
- return [2 /*return*/];
110
- }
111
- response.send(bytes);
112
- return [3 /*break*/, 6];
113
- case 5:
114
- error_1 = _b.sent();
115
- console.error("Error fetching file (".concat(fileId, "):"), error_1);
116
- response.status(500).send('Internal Server Error');
117
- return [3 /*break*/, 6];
118
- case 6: return [2 /*return*/];
79
+ error_1 = _a.sent();
80
+ FileResponder_1.FileResponder.fail(fileId, error_1, response);
81
+ return [3 /*break*/, 5];
82
+ case 5: return [2 /*return*/];
119
83
  }
120
84
  });
121
85
  }); },
@@ -1 +1 @@
1
- {"version":3,"file":"getFile.js","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8CAAgD;AAChD,wCAA2C;AAC3C,yCAA+C;AAE/C;;;;;;;;;;;;;;;GAeG;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"}
1
+ {"version":3,"file":"getFile.js","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8CAAgD;AAChD,wCAA2C;AAC3C,iDAAgD;AAEhD;;;;;;;GAOG;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;;;;oBAGlB,qBAAM,IAAA,4BAAc,GAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAA;;oBAA7C,IAAI,GAAG,SAAsC;oBACnD,IAAI,CAAC,IAAI,EAAE;wBACT,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBAC5C,sBAAO;qBACR;oBACD,qBAAM,6BAAa,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAA;;oBAAlD,SAAkD,CAAC;;;;oBAEnD,6BAAa,CAAC,IAAI,CAAC,MAAM,EAAE,OAAK,EAAE,QAAQ,CAAC,CAAC;;;;;SAE/C;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { Route } from '@proteinjs/server-api';
2
+ /**
3
+ * Serves a file's VARIANT of a kind (`preview`, `stage`) — the one URL a surface that draws a
4
+ * variant asks by (a plain `<img src>`, an off-DOM warm-up), whether or not the file has it yet:
5
+ *
6
+ * - the variant the row names is served as any File is ({@link FileResponder} — a 302 to ITS
7
+ * signed URL or the proxy, ITS headers; a non-owner is served ITS copy for others);
8
+ * - a file WITHOUT the variant derives it on this first request, once, through the registered
9
+ * `FileVariantMaker` (`FileStorage.getVariant` — a file stored before the kind existed, or by a
10
+ * producer that did not derive it; the row's word is decided in one transaction, so a second
11
+ * request never re-derives);
12
+ * - a file the maker does not apply to (a clip, a picture already small), or no maker at all, is
13
+ * served AS ITSELF — the consumer always gets a picture from this URL and never branches.
14
+ *
15
+ * Auth as `/file/:id`: logged-in, then the original's `getFile` read is the access decision (a
16
+ * variant is readable by whoever can read its original). A kind the library does not know is a
17
+ * 404, as a file that is not there.
18
+ */
19
+ export declare const getFileVariant: Route;
20
+ //# sourceMappingURL=getFileVariant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileVariant.d.ts","sourceRoot":"","sources":["../../../src/routes/getFileVariant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAM9C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,EAAE,KA6B5B,CAAC"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getFileVariant = void 0;
40
+ var user_1 = require("@proteinjs/user");
41
+ var FileStorage_1 = require("../FileStorage");
42
+ var FileVariantMaker_1 = require("../FileVariantMaker");
43
+ var FileResponder_1 = require("./FileResponder");
44
+ /**
45
+ * Serves a file's VARIANT of a kind (`preview`, `stage`) — the one URL a surface that draws a
46
+ * variant asks by (a plain `<img src>`, an off-DOM warm-up), whether or not the file has it yet:
47
+ *
48
+ * - the variant the row names is served as any File is ({@link FileResponder} — a 302 to ITS
49
+ * signed URL or the proxy, ITS headers; a non-owner is served ITS copy for others);
50
+ * - a file WITHOUT the variant derives it on this first request, once, through the registered
51
+ * `FileVariantMaker` (`FileStorage.getVariant` — a file stored before the kind existed, or by a
52
+ * producer that did not derive it; the row's word is decided in one transaction, so a second
53
+ * request never re-derives);
54
+ * - a file the maker does not apply to (a clip, a picture already small), or no maker at all, is
55
+ * served AS ITSELF — the consumer always gets a picture from this URL and never branches.
56
+ *
57
+ * Auth as `/file/:id`: logged-in, then the original's `getFile` read is the access decision (a
58
+ * variant is readable by whoever can read its original). A kind the library does not know is a
59
+ * 404, as a file that is not there.
60
+ */
61
+ exports.getFileVariant = {
62
+ path: '/file/:id/variant/:kind',
63
+ method: 'get',
64
+ onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
65
+ var fileId, kind, fileStorage, served, _a, error_1;
66
+ var _b;
67
+ return __generator(this, function (_c) {
68
+ switch (_c.label) {
69
+ case 0:
70
+ if (!user_1.UserAuth.isLoggedIn()) {
71
+ response.status(401).send('User not logged in');
72
+ return [2 /*return*/];
73
+ }
74
+ fileId = request.params.id;
75
+ kind = request.params.kind;
76
+ if (!FileVariantMaker_1.FILE_VARIANT_KINDS.includes(kind)) {
77
+ response.status(404).send('File not found');
78
+ return [2 /*return*/];
79
+ }
80
+ _c.label = 1;
81
+ case 1:
82
+ _c.trys.push([1, 7, , 8]);
83
+ fileStorage = new FileStorage_1.FileStorage();
84
+ return [4 /*yield*/, fileStorage.getVariant(fileId, kind)];
85
+ case 2:
86
+ if (!((_b = (_c.sent())) !== null && _b !== void 0)) return [3 /*break*/, 3];
87
+ _a = _b;
88
+ return [3 /*break*/, 5];
89
+ case 3: return [4 /*yield*/, fileStorage.getFile(fileId)];
90
+ case 4:
91
+ _a = (_c.sent());
92
+ _c.label = 5;
93
+ case 5:
94
+ served = _a;
95
+ if (!served) {
96
+ response.status(404).send('File not found');
97
+ return [2 /*return*/];
98
+ }
99
+ return [4 /*yield*/, FileResponder_1.FileResponder.serve(served, request, response)];
100
+ case 6:
101
+ _c.sent();
102
+ return [3 /*break*/, 8];
103
+ case 7:
104
+ error_1 = _c.sent();
105
+ FileResponder_1.FileResponder.fail(fileId, error_1, response);
106
+ return [3 /*break*/, 8];
107
+ case 8: return [2 /*return*/];
108
+ }
109
+ });
110
+ }); },
111
+ };
112
+ //# sourceMappingURL=getFileVariant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileVariant.js","sourceRoot":"","sources":["../../../src/routes/getFileVariant.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wCAA2C;AAC3C,8CAA6C;AAC7C,wDAA0E;AAC1E,iDAAgD;AAEhD;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,cAAc,GAAU;IACnC,IAAI,EAAE,yBAAyB;IAC/B,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,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAuB,CAAC;oBACpD,IAAI,CAAC,qCAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;wBACtC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBAC5C,sBAAO;qBACR;;;;oBAEO,WAAW,GAAG,IAAI,yBAAW,EAAE,CAAC;oBAGtB,qBAAM,WAAW,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,EAAA;;gCAA3C,CAAC,SAA0C,CAAC;;;wBAAK,qBAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAA;;oBAAlC,KAAA,CAAC,SAAiC,CAAC,CAAA;;;oBAA5F,MAAM,KAAsF;oBAClG,IAAI,CAAC,MAAM,EAAE;wBACX,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBAC5C,sBAAO;qBACR;oBACD,qBAAM,6BAAa,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAA;;oBAApD,SAAoD,CAAC;;;;oBAErD,6BAAa,CAAC,IAAI,CAAC,MAAM,EAAE,OAAK,EAAE,QAAQ,CAAC,CAAC;;;;;SAE/C;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { FileVariantKind } from '../FileVariantMaker';
2
+ /** The URL a surface asks a file's variant of a kind by — served by `GET /file/:id/variant/:kind` (the variant, or the file itself when none applies). */
3
+ export declare const getFileVariantRoute: {
4
+ path: (fileId: string, kind: FileVariantKind) => string;
5
+ method: string;
6
+ };
7
+ //# sourceMappingURL=getFileVariantRoute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileVariantRoute.d.ts","sourceRoot":"","sources":["../../../src/routes/getFileVariantRoute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,0JAA0J;AAC1J,eAAO,MAAM,mBAAmB;mBACf,MAAM,QAAQ,eAAe;;CAE7C,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFileVariantRoute = void 0;
4
+ /** The URL a surface asks a file's variant of a kind by — served by `GET /file/:id/variant/:kind` (the variant, or the file itself when none applies). */
5
+ exports.getFileVariantRoute = {
6
+ path: function (fileId, kind) { return "/file/".concat(fileId, "/variant/").concat(kind); },
7
+ method: 'get',
8
+ };
9
+ //# sourceMappingURL=getFileVariantRoute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileVariantRoute.js","sourceRoot":"","sources":["../../../src/routes/getFileVariantRoute.ts"],"names":[],"mappings":";;;AAEA,0JAA0J;AAC7I,QAAA,mBAAmB,GAAG;IACjC,IAAI,EAAE,UAAC,MAAc,EAAE,IAAqB,IAAK,OAAA,gBAAS,MAAM,sBAAY,IAAI,CAAE,EAAjC,CAAiC;IAClF,MAAM,EAAE,KAAK;CACd,CAAC"}
@@ -8,8 +8,41 @@ export interface File extends ScopedRecord {
8
8
  * Optional preview — another `File` (stored the same way, in GCS) used as a preview/thumbnail of
9
9
  * this file's content, e.g. a recording GIF's preview frame. A reference, not an inline blob, so
10
10
  * the bytes stay out of the DB row (consistent with the storage model); deleted with this file.
11
+ *
12
+ * A VARIANT — one of the derived Files of this file the {@link FileVariantMaker} seam makes
13
+ * (`preview`, `stage`): the same lifecycle for each — named here, made once (at ingest with the
14
+ * bytes in hand, or lazily on the first request through `FileStorage.getVariant`), dropped when
15
+ * this file's bytes change (`FileStorage.updateFileData`), deleted with this file (cascade), and
16
+ * readable by whoever can read this file (`variantOf`).
11
17
  */
12
- preview?: Reference<File>;
18
+ preview?: Reference<File> | null;
19
+ /**
20
+ * The stage variant — a second derived `File` sized for the surface that shows the picture large
21
+ * but not full-screen (a 1600 px longest-edge rendition where the preview is a 512 px thumbnail):
22
+ * the same mechanism as `preview` in every respect (see there). Absent on files made before the
23
+ * seam or of a kind the maker does not apply to (a video, an animated picture); the first request
24
+ * derives it once. A viewer that wants every pixel keeps drawing the original.
25
+ */
26
+ stage?: Reference<File> | null;
27
+ /**
28
+ * For a variant (`preview`/`stage`) made by the seam: the File it was derived from. The
29
+ * variant's reachability is its original's — a reader who can read the original (their own
30
+ * scope, or a row the shared-content leg vouches for) can read the variant by its own id, even
31
+ * when no content row names the variant (a variant derived after the content was placed). Never
32
+ * set on a `copyForOthers` copy, whose own id is reachable by nobody but the owner. No cascade
33
+ * in either direction: the original's own reference (`preview`/`stage`) deletes the variant.
34
+ */
35
+ variantOf?: Reference<File> | null;
36
+ /**
37
+ * The copy of this file served to anyone who is not its owner — another `File` (the same
38
+ * store, the owner's scope), made ONCE by the registered {@link FileCopyForOthers} the first
39
+ * time a non-owner reads the bytes, named here so every later read serves it without remaking
40
+ * it. `null`/absent means no copy has been made yet: the next non-owner read makes one when the
41
+ * maker applies to this file, and serves the original when it does not. Dropped when the
42
+ * original's bytes change (`FileStorage.updateFileData`) and deleted with this file (cascade).
43
+ * The owner is never served it — their own scoped read serves the original.
44
+ */
45
+ copyForOthers?: Reference<File> | null;
13
46
  /**
14
47
  * Media metadata — generic file facts (an image/video's pixel dimensions, a video/audio
15
48
  * duration) every consumer needs to render without loading bytes, e.g. reserving a media box's
@@ -46,6 +79,21 @@ export interface File extends ScopedRecord {
46
79
  * an integrity fact. Absent for files written before the column existed.
47
80
  */
48
81
  contentHash?: string;
82
+ /**
83
+ * Producer attribution — HOW these bytes came to exist (e.g. a user upload, a browser
84
+ * capture, model generation, a rendered mockup). Same layer ruling as `width`/`height`:
85
+ * provenance is a generic file fact consumers need without a join — e.g. evidence surfaces
86
+ * that must refuse non-capture media as proof. Set by ingest paths that know their producer;
87
+ * absent for everything else. Values are the producing domain's vocabulary, not enumerated
88
+ * here.
89
+ */
90
+ origin?: string;
91
+ /**
92
+ * The id of the model that made the bytes, when a model did — `origin` names the KIND of
93
+ * producer, this names the one. Absent for every other producer. Read by a consumer that
94
+ * labels a made file with what made it, without a join.
95
+ */
96
+ originModel?: string;
49
97
  }
50
98
  export declare class FileTable extends Table<File> {
51
99
  name: string;
@@ -59,7 +107,10 @@ export declare class FileTable extends Table<File> {
59
107
  name: import("@proteinjs/db").Column<string, any>;
60
108
  type: import("@proteinjs/db").Column<string, any>;
61
109
  size: import("@proteinjs/db").Column<number, any>;
62
- preview: import("@proteinjs/db").Column<Reference<File> | undefined, any>;
110
+ preview: import("@proteinjs/db").Column<Reference<File> | null | undefined, any>;
111
+ stage: import("@proteinjs/db").Column<Reference<File> | null | undefined, any>;
112
+ variantOf: import("@proteinjs/db").Column<Reference<File> | null | undefined, any>;
113
+ copyForOthers: import("@proteinjs/db").Column<Reference<File> | null | undefined, any>;
63
114
  width: import("@proteinjs/db").Column<number | undefined, any>;
64
115
  height: import("@proteinjs/db").Column<number | undefined, any>;
65
116
  durationMs: import("@proteinjs/db").Column<number | undefined, any>;
@@ -70,6 +121,8 @@ export declare class FileTable extends Table<File> {
70
121
  licenseUrl: import("@proteinjs/db").Column<string | undefined, any>;
71
122
  attribution: import("@proteinjs/db").Column<string | undefined, any>;
72
123
  contentHash: import("@proteinjs/db").Column<string | undefined, any>;
124
+ origin: import("@proteinjs/db").Column<string | undefined, any>;
125
+ originModel: import("@proteinjs/db").Column<string | undefined, any>;
73
126
  } & {};
74
127
  indexes: {
75
128
  columns: (keyof File)[];
@@ -1 +1 @@
1
- {"version":3,"file":"FileTable.d.ts","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgD,SAAS,EAAc,MAAM,eAAe,CAAC;AAC3G,OAAO,EAAE,YAAY,EAA8C,MAAM,iBAAiB,CAAC;AAI3F,MAAM,WAAW,IAAK,SAAQ,YAAY;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,SAAU,SAAQ,KAAK,CAAC,IAAI,CAAC;IACjC,IAAI,SAAmB;IACvB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAO9B;IACK,OAAO;;;;;;;;;;;;;;;;;;;;WAmBX;IAGI,OAAO;;;QAA0F;CAGzG"}
1
+ {"version":3,"file":"FileTable.d.ts","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgD,SAAS,EAAc,MAAM,eAAe,CAAC;AAC3G,OAAO,EAAE,YAAY,EAA8C,MAAM,iBAAiB,CAAC;AAI3F,MAAM,WAAW,IAAK,SAAQ,YAAY;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACjC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACnC;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,SAAU,SAAQ,KAAK,CAAC,IAAI,CAAC;IACjC,IAAI,SAAmB;IACvB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAO9B;IACK,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;WA2BX;IAGI,OAAO;;;QAA0F;CAGzG"}
@@ -38,6 +38,12 @@ var FileTable = /** @class */ (function (_super) {
38
38
  size: new db_1.IntegerColumn('size'),
39
39
  // Self-reference (the preview is itself a File). cascadeDelete: removing a file removes its preview.
40
40
  preview: new db_1.ReferenceColumn('preview', FILE_TABLE_NAME, true),
41
+ // Self-reference (the stage variant is itself a File). cascadeDelete: removing a file removes its stage variant.
42
+ stage: new db_1.ReferenceColumn('stage', FILE_TABLE_NAME, true),
43
+ // Self-reference (a variant names its original). No cascade: the original's own reference deletes the variant.
44
+ variantOf: new db_1.ReferenceColumn('variant_of', FILE_TABLE_NAME, false),
45
+ // Self-reference (the copy is itself a File). cascadeDelete: removing a file removes the copy others were served.
46
+ copyForOthers: new db_1.ReferenceColumn('copy_for_others', FILE_TABLE_NAME, true),
41
47
  width: new db_1.IntegerColumn('width'),
42
48
  height: new db_1.IntegerColumn('height'),
43
49
  durationMs: new db_1.IntegerColumn('duration_ms'),
@@ -51,6 +57,8 @@ var FileTable = /** @class */ (function (_super) {
51
57
  licenseUrl: new db_1.StringColumn('license_url', {}, 'MAX'),
52
58
  attribution: new db_1.StringColumn('attribution', {}, 'MAX'),
53
59
  contentHash: new db_1.StringColumn('content_hash', {}, 64),
60
+ origin: new db_1.StringColumn('origin', undefined, 50),
61
+ originModel: new db_1.StringColumn('origin_model', undefined, 200),
54
62
  });
55
63
  // Dedup lookup path: find the caller's existing copy of these bytes (content_hash is only
56
64
  // ever queried per-user — createScopedIndex prefixes the scope column).
@@ -1 +1 @@
1
- {"version":3,"file":"FileTable.js","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,oCAA2G;AAC3G,wCAA2F;AAE3F,IAAM,eAAe,GAAG,MAAM,CAAC;AAkD/B;IAA+B,6BAAW;IAA1C;QAAA,qEAmCC;QAlCQ,UAAI,GAAG,eAAe,CAAC;QACvB,UAAI,GAAwB;YACjC,EAAE,EAAE;gBACF,GAAG,EAAE,eAAe;aACrB;YACD,OAAO,EAAE;gBACP,GAAG,EAAE,eAAe;aACrB;SACF,CAAC;QACK,aAAO,GAAG,IAAA,8BAAuB,EAAO;YAC7C,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,IAAI,EAAE,IAAI,kBAAa,CAAC,MAAM,CAAC;YAC/B,qGAAqG;YACrG,OAAO,EAAE,IAAI,oBAAe,CAAO,SAAS,EAAE,eAAe,EAAE,IAAI,CAAC;YACpE,KAAK,EAAE,IAAI,kBAAa,CAAC,OAAO,CAAC;YACjC,MAAM,EAAE,IAAI,kBAAa,CAAC,QAAQ,CAAC;YACnC,UAAU,EAAE,IAAI,kBAAa,CAAC,aAAa,CAAC;YAC5C,yFAAyF;YACzF,SAAS,EAAE,IAAI,iBAAY,CAAC,YAAY,EAAE,EAAE,EAAE,KAAK,CAAC;YACpD,aAAa,EAAE,IAAI,iBAAY,CAAC,iBAAiB,EAAE,EAAE,EAAE,KAAK,CAAC;YAC7D,WAAW,EAAE,IAAI,eAAU,CAAC,cAAc,CAAC;YAC3C,wFAAwF;YACxF,iEAAiE;YACjE,OAAO,EAAE,IAAI,iBAAY,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;YAC5C,UAAU,EAAE,IAAI,iBAAY,CAAC,aAAa,EAAE,EAAE,EAAE,KAAK,CAAC;YACtD,WAAW,EAAE,IAAI,iBAAY,CAAC,aAAa,EAAE,EAAE,EAAE,KAAK,CAAC;YACvD,WAAW,EAAE,IAAI,iBAAY,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,CAAC;SACtD,CAAC,CAAC;QACH,0FAA0F;QAC1F,wEAAwE;QACjE,aAAO,GAAG,CAAC,IAAA,wBAAiB,EAAO,EAAE,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;;QACxG,6FAA6F;QAC7F,8FAA8F;IAChG,CAAC;IAAD,gBAAC;AAAD,CAAC,AAnCD,CAA+B,UAAK,GAmCnC;AAnCY,8BAAS"}
1
+ {"version":3,"file":"FileTable.js","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,oCAA2G;AAC3G,wCAA2F;AAE3F,IAAM,eAAe,GAAG,MAAM,CAAC;AAkG/B;IAA+B,6BAAW;IAA1C;QAAA,qEA2CC;QA1CQ,UAAI,GAAG,eAAe,CAAC;QACvB,UAAI,GAAwB;YACjC,EAAE,EAAE;gBACF,GAAG,EAAE,eAAe;aACrB;YACD,OAAO,EAAE;gBACP,GAAG,EAAE,eAAe;aACrB;SACF,CAAC;QACK,aAAO,GAAG,IAAA,8BAAuB,EAAO;YAC7C,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,IAAI,EAAE,IAAI,kBAAa,CAAC,MAAM,CAAC;YAC/B,qGAAqG;YACrG,OAAO,EAAE,IAAI,oBAAe,CAAO,SAAS,EAAE,eAAe,EAAE,IAAI,CAAC;YACpE,iHAAiH;YACjH,KAAK,EAAE,IAAI,oBAAe,CAAO,OAAO,EAAE,eAAe,EAAE,IAAI,CAAC;YAChE,+GAA+G;YAC/G,SAAS,EAAE,IAAI,oBAAe,CAAO,YAAY,EAAE,eAAe,EAAE,KAAK,CAAC;YAC1E,kHAAkH;YAClH,aAAa,EAAE,IAAI,oBAAe,CAAO,iBAAiB,EAAE,eAAe,EAAE,IAAI,CAAC;YAClF,KAAK,EAAE,IAAI,kBAAa,CAAC,OAAO,CAAC;YACjC,MAAM,EAAE,IAAI,kBAAa,CAAC,QAAQ,CAAC;YACnC,UAAU,EAAE,IAAI,kBAAa,CAAC,aAAa,CAAC;YAC5C,yFAAyF;YACzF,SAAS,EAAE,IAAI,iBAAY,CAAC,YAAY,EAAE,EAAE,EAAE,KAAK,CAAC;YACpD,aAAa,EAAE,IAAI,iBAAY,CAAC,iBAAiB,EAAE,EAAE,EAAE,KAAK,CAAC;YAC7D,WAAW,EAAE,IAAI,eAAU,CAAC,cAAc,CAAC;YAC3C,wFAAwF;YACxF,iEAAiE;YACjE,OAAO,EAAE,IAAI,iBAAY,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;YAC5C,UAAU,EAAE,IAAI,iBAAY,CAAC,aAAa,EAAE,EAAE,EAAE,KAAK,CAAC;YACtD,WAAW,EAAE,IAAI,iBAAY,CAAC,aAAa,EAAE,EAAE,EAAE,KAAK,CAAC;YACvD,WAAW,EAAE,IAAI,iBAAY,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,CAAC;YACrD,MAAM,EAAE,IAAI,iBAAY,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC;YACjD,WAAW,EAAE,IAAI,iBAAY,CAAC,cAAc,EAAE,SAAS,EAAE,GAAG,CAAC;SAC9D,CAAC,CAAC;QACH,0FAA0F;QAC1F,wEAAwE;QACjE,aAAO,GAAG,CAAC,IAAA,wBAAiB,EAAO,EAAE,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;;QACxG,6FAA6F;QAC7F,8FAA8F;IAChG,CAAC;IAAD,gBAAC;AAAD,CAAC,AA3CD,CAA+B,UAAK,GA2CnC;AA3CY,8BAAS"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DbFileStorageDriverMissingBytes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DbFileStorageDriverMissingBytes.test.d.ts","sourceRoot":"","sources":["../../test/DbFileStorageDriverMissingBytes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ var db_1 = require("@proteinjs/db");
40
+ var tables_1 = require("../src/tables/tables");
41
+ var FileStorage_1 = require("../src/FileStorage");
42
+ var FileStorageError_1 = require("../src/FileStorageError");
43
+ var DbFileStorageDriver_1 = require("../src/DbFileStorageDriver");
44
+ var FileTestEnvironment_1 = require("./FileTestEnvironment");
45
+ /**
46
+ * The driver contract has one answer for bytes that are not there: the `not-found` code. An
47
+ * object store says it for a missing object; this driver's store is the chunk table, where
48
+ * "nothing stored" and "an empty file" both used to read as zero chunk rows and came back as `''`
49
+ * — so a caller could not tell a lost file from an empty one, and code that branched on the
50
+ * object store's error never ran against this driver.
51
+ */
52
+ var testEnv = new FileTestEnvironment_1.FileTestEnvironment();
53
+ var driver = new DbFileStorageDriver_1.DbFileStorageDriver();
54
+ var owner;
55
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
56
+ return __generator(this, function (_a) {
57
+ switch (_a.label) {
58
+ case 0: return [4 /*yield*/, testEnv.beforeAll()];
59
+ case 1:
60
+ _a.sent();
61
+ testEnv.setDriver(driver);
62
+ return [4 /*yield*/, testEnv.createUser({ name: 'File owner', email: 'missing-bytes-owner@test.local' })];
63
+ case 2:
64
+ owner = _a.sent();
65
+ testEnv.actAs(owner);
66
+ return [2 /*return*/];
67
+ }
68
+ });
69
+ }); });
70
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
71
+ return __generator(this, function (_a) {
72
+ switch (_a.label) {
73
+ case 0: return [4 /*yield*/, testEnv.afterAll()];
74
+ case 1:
75
+ _a.sent();
76
+ return [2 /*return*/];
77
+ }
78
+ });
79
+ }); });
80
+ var chunkRows = function (fileId) { return __awaiter(void 0, void 0, void 0, function () {
81
+ return __generator(this, function (_a) {
82
+ switch (_a.label) {
83
+ case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().query(tables_1.tables.FileData, new db_1.QueryBuilderFactory().getQueryBuilder(tables_1.tables.FileData, { file: fileId }))];
84
+ case 1: return [2 /*return*/, _a.sent()];
85
+ }
86
+ });
87
+ }); };
88
+ describe('DbFileStorageDriver reports bytes that are not there as not-found', function () {
89
+ it('a file row whose bytes are gone is not-found, not an empty string', function () { return __awaiter(void 0, void 0, void 0, function () {
90
+ var bytes, file, failure;
91
+ return __generator(this, function (_a) {
92
+ switch (_a.label) {
93
+ case 0:
94
+ bytes = Buffer.from('the bytes of a picture');
95
+ return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: 'holiday.jpg', type: 'image/jpeg', size: bytes.length }, bytes.toString('base64'))];
96
+ case 1:
97
+ file = _a.sent();
98
+ return [4 /*yield*/, driver.deleteFile(file.id)];
99
+ case 2:
100
+ _a.sent();
101
+ return [4 /*yield*/, driver.getFileData(file.id).catch(function (error) { return error; })];
102
+ case 3:
103
+ failure = _a.sent();
104
+ expect(FileStorageError_1.FileStorageError.isNotFound(failure)).toBe(true);
105
+ expect(failure.message).toEqual("getFileData failed for file ".concat(file.id, ": not-found"));
106
+ return [2 /*return*/];
107
+ }
108
+ });
109
+ }); });
110
+ it('an id that names no file at all is not-found', function () { return __awaiter(void 0, void 0, void 0, function () {
111
+ var failure;
112
+ return __generator(this, function (_a) {
113
+ switch (_a.label) {
114
+ case 0: return [4 /*yield*/, driver.getFileData('no-such-file-id').catch(function (error) { return error; })];
115
+ case 1:
116
+ failure = _a.sent();
117
+ expect(FileStorageError_1.FileStorageError.isNotFound(failure)).toBe(true);
118
+ return [2 /*return*/];
119
+ }
120
+ });
121
+ }); });
122
+ it('an empty file is stored, and reads back as empty', function () { return __awaiter(void 0, void 0, void 0, function () {
123
+ var file, _a, _b;
124
+ return __generator(this, function (_c) {
125
+ switch (_c.label) {
126
+ case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: 'empty.txt', type: 'text/plain', size: 0 }, '')];
127
+ case 1:
128
+ file = _c.sent();
129
+ _a = expect;
130
+ return [4 /*yield*/, chunkRows(file.id)];
131
+ case 2:
132
+ _a.apply(void 0, [_c.sent()]).toHaveLength(1);
133
+ _b = expect;
134
+ return [4 /*yield*/, driver.getFileData(file.id)];
135
+ case 3:
136
+ _b.apply(void 0, [_c.sent()]).toEqual('');
137
+ return [2 /*return*/];
138
+ }
139
+ });
140
+ }); });
141
+ it('a file overwritten with nothing stays stored, whatever its row says about its size', function () { return __awaiter(void 0, void 0, void 0, function () {
142
+ var bytes, file, _a;
143
+ return __generator(this, function (_b) {
144
+ switch (_b.label) {
145
+ case 0:
146
+ bytes = Buffer.from('first');
147
+ return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: 'note.txt', type: 'text/plain', size: bytes.length }, bytes.toString('base64'))];
148
+ case 1:
149
+ file = _b.sent();
150
+ return [4 /*yield*/, driver.updateFileData(file.id, '')];
151
+ case 2:
152
+ _b.sent();
153
+ _a = expect;
154
+ return [4 /*yield*/, driver.getFileData(file.id)];
155
+ case 3:
156
+ _a.apply(void 0, [_b.sent()]).toEqual('');
157
+ return [2 /*return*/];
158
+ }
159
+ });
160
+ }); });
161
+ it('an empty file written before empty files had a chunk row still reads back as empty', function () { return __awaiter(void 0, void 0, void 0, function () {
162
+ var file, _a, _b;
163
+ return __generator(this, function (_c) {
164
+ switch (_c.label) {
165
+ case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: 'old-empty.txt', type: 'text/plain', size: 0 }, '')];
166
+ case 1:
167
+ file = _c.sent();
168
+ return [4 /*yield*/, driver.deleteFile(file.id)];
169
+ case 2:
170
+ _c.sent();
171
+ _a = expect;
172
+ return [4 /*yield*/, chunkRows(file.id)];
173
+ case 3:
174
+ _a.apply(void 0, [_c.sent()]).toHaveLength(0);
175
+ _b = expect;
176
+ return [4 /*yield*/, driver.getFileData(file.id)];
177
+ case 4:
178
+ _b.apply(void 0, [_c.sent()]).toEqual('');
179
+ return [2 /*return*/];
180
+ }
181
+ });
182
+ }); });
183
+ });
184
+ //# sourceMappingURL=DbFileStorageDriverMissingBytes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DbFileStorageDriverMissingBytes.test.js","sourceRoot":"","sources":["../../test/DbFileStorageDriverMissingBytes.test.ts"],"names":[],"mappings":"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"}