@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
@@ -0,0 +1,1008 @@
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 reflection_1 = require("@proteinjs/reflection");
41
+ var user_1 = require("@proteinjs/user");
42
+ var tables_1 = require("../src/tables/tables");
43
+ var FileStorage_1 = require("../src/FileStorage");
44
+ var getFile_1 = require("../src/routes/getFile");
45
+ var getFileVariant_1 = require("../src/routes/getFileVariant");
46
+ var getFileVariantRoute_1 = require("../src/routes/getFileVariantRoute");
47
+ var FileTestEnvironment_1 = require("./FileTestEnvironment");
48
+ /**
49
+ * A derived File of a File — the preview and the stage variant — has ONE lifecycle, the library's:
50
+ * made by the registered maker (at ingest with the bytes in hand, or once on the first request from
51
+ * a surface that draws it), stored as the owner's own File naming its original, named on the
52
+ * original's row, dropped when the original's bytes change, deleted with the original, readable by
53
+ * whoever can read the original, and served like any File — through the same non-owner copy rule.
54
+ * This suite proves the seam contract with a stub maker (the real one — a picture's 512 px thumbnail
55
+ * and its 1600 px rendition — lives in the package that knows pictures).
56
+ *
57
+ * The stub maker marks what it makes: `PREVIEW:`/`STAGE:` and the first bytes of the original.
58
+ */
59
+ var originalBytes = Buffer.concat([Buffer.from('ORIGINAL:'), Buffer.from(Array.from({ length: 64 }, function (_, i) { return i; }))]);
60
+ var variantOf = function (kind, bytes) {
61
+ return Buffer.concat([Buffer.from("".concat(kind.toUpperCase(), ":")), bytes.subarray(0, 8)]);
62
+ };
63
+ var VARIANT_EDGE = { preview: 512, stage: 1600 };
64
+ /** What the copy-for-others maker takes off a copy (the location marker of the real one). */
65
+ var COPY_MARK = Buffer.from('-FOR-OTHERS');
66
+ /** In-memory proxy-shape driver (no signed URLs) — the DbFileStorageDriver serving shape. */
67
+ var ProxyDriver = /** @class */ (function () {
68
+ function ProxyDriver() {
69
+ this.store = new Map();
70
+ /** Every id whose bytes were read, in order — what a derivation costs the store. */
71
+ this.reads = [];
72
+ /** Every id whose bytes were deleted, in order. */
73
+ this.deletes = [];
74
+ }
75
+ ProxyDriver.prototype.createFile = function (file, fileData) {
76
+ return __awaiter(this, void 0, void 0, function () {
77
+ return __generator(this, function (_a) {
78
+ switch (_a.label) {
79
+ case 0:
80
+ this.store.set(file.id, Buffer.from(fileData, 'base64'));
81
+ return [4 /*yield*/, atBarrier()];
82
+ case 1:
83
+ _a.sent(); // the racing case: every variant's bytes are stored before any row is asked its word
84
+ return [2 /*return*/];
85
+ }
86
+ });
87
+ });
88
+ };
89
+ ProxyDriver.prototype.getFileData = function (fileId) {
90
+ return __awaiter(this, void 0, void 0, function () {
91
+ var data;
92
+ return __generator(this, function (_a) {
93
+ this.reads.push(fileId);
94
+ data = this.store.get(fileId);
95
+ if (data === undefined) {
96
+ throw new Error("No such object: ".concat(fileId));
97
+ }
98
+ return [2 /*return*/, data.toString('base64')];
99
+ });
100
+ });
101
+ };
102
+ ProxyDriver.prototype.updateFileData = function (fileId, data) {
103
+ return __awaiter(this, void 0, void 0, function () {
104
+ return __generator(this, function (_a) {
105
+ this.store.set(fileId, Buffer.from(data, 'base64'));
106
+ return [2 /*return*/];
107
+ });
108
+ });
109
+ };
110
+ ProxyDriver.prototype.deleteFile = function (fileId) {
111
+ return __awaiter(this, void 0, void 0, function () {
112
+ return __generator(this, function (_a) {
113
+ this.deletes.push(fileId);
114
+ this.store.delete(fileId);
115
+ return [2 /*return*/];
116
+ });
117
+ });
118
+ };
119
+ return ProxyDriver;
120
+ }());
121
+ var ResponseRecorder = /** @class */ (function () {
122
+ function ResponseRecorder() {
123
+ this.headers = {};
124
+ }
125
+ ResponseRecorder.prototype.status = function (code) {
126
+ this.statusCode = code;
127
+ return this;
128
+ };
129
+ ResponseRecorder.prototype.send = function (body) {
130
+ this.body = body;
131
+ return this;
132
+ };
133
+ ResponseRecorder.prototype.setHeader = function (name, value) {
134
+ this.headers[name.toLowerCase()] = value;
135
+ };
136
+ ResponseRecorder.prototype.redirect = function () {
137
+ throw new Error('the proxy driver never redirects');
138
+ };
139
+ return ResponseRecorder;
140
+ }());
141
+ var invokeRoute = function (fileId) { return __awaiter(void 0, void 0, void 0, function () {
142
+ var response;
143
+ return __generator(this, function (_a) {
144
+ switch (_a.label) {
145
+ case 0:
146
+ response = new ResponseRecorder();
147
+ return [4 /*yield*/, getFile_1.getFile.onRequest({ params: { id: fileId }, headers: {} }, response)];
148
+ case 1:
149
+ _a.sent();
150
+ return [2 /*return*/, response];
151
+ }
152
+ });
153
+ }); };
154
+ /** `GET /file/:id/variant/:kind` — the URL a surface asks for a file's variant by (a plain `<img src>`). */
155
+ var invokeVariantRoute = function (fileId, kind) { return __awaiter(void 0, void 0, void 0, function () {
156
+ var response;
157
+ return __generator(this, function (_a) {
158
+ switch (_a.label) {
159
+ case 0:
160
+ response = new ResponseRecorder();
161
+ return [4 /*yield*/, getFileVariant_1.getFileVariant.onRequest({ params: { id: fileId, kind: kind }, headers: {} }, response)];
162
+ case 1:
163
+ _a.sent();
164
+ return [2 /*return*/, response];
165
+ }
166
+ });
167
+ }); };
168
+ var testEnv = new FileTestEnvironment_1.FileTestEnvironment();
169
+ var objectCache = function () { return reflection_1.SourceRepository.get().objectCache; };
170
+ var owner;
171
+ var recipient;
172
+ var stranger;
173
+ var reachableFileIds = new Set();
174
+ /** Every call the variant maker took: which file, which kind, the bytes it was handed. */
175
+ var makerCalls = [];
176
+ /** The kinds the stub maker applies to (of a picture) — the suite's own rule. */
177
+ var kindsApplied = new Set(['preview', 'stage']);
178
+ /** A barrier for the racing case — `makerBarrier = n` holds every maker call until n are inside, then releases them on one tick. */
179
+ var makerBarrier = 0;
180
+ var makerWaiting = [];
181
+ /** A hold for the in-flight case — the maker waits on it (when set) so readers can arrive while one derivation runs. */
182
+ var makerHold;
183
+ /** The maker cannot make anything of these bytes (a picture the decoder cannot read). */
184
+ var makerRefuses = false;
185
+ var atBarrier = function () { return __awaiter(void 0, void 0, void 0, function () {
186
+ return __generator(this, function (_a) {
187
+ switch (_a.label) {
188
+ case 0:
189
+ if (makerBarrier <= 0) {
190
+ return [2 /*return*/];
191
+ }
192
+ return [4 /*yield*/, new Promise(function (resolve) {
193
+ makerWaiting.push(resolve);
194
+ if (makerWaiting.length >= makerBarrier) {
195
+ var release = makerWaiting;
196
+ makerWaiting = [];
197
+ release.forEach(function (go) { return go(); });
198
+ }
199
+ })];
200
+ case 1:
201
+ _a.sent();
202
+ return [2 /*return*/];
203
+ }
204
+ });
205
+ }); };
206
+ var registerVariantMaker = function () {
207
+ objectCache()['@proteinjs/db-file/FileVariantMaker'] = [
208
+ {
209
+ appliesTo: function (file, kind) { return file.type.startsWith('image/') && kindsApplied.has(kind); },
210
+ make: function (file, bytes, kind) { return __awaiter(void 0, void 0, void 0, function () {
211
+ return __generator(this, function (_a) {
212
+ switch (_a.label) {
213
+ case 0:
214
+ makerCalls.push({ fileId: file.id, kind: kind, bytes: Buffer.from(bytes) });
215
+ return [4 /*yield*/, atBarrier()];
216
+ case 1:
217
+ _a.sent();
218
+ return [4 /*yield*/, makerHold];
219
+ case 2:
220
+ _a.sent();
221
+ if (makerRefuses) {
222
+ throw new Error('the decoder cannot decode these bytes');
223
+ }
224
+ return [2 /*return*/, {
225
+ bytes: variantOf(kind, bytes),
226
+ type: 'image/webp',
227
+ width: VARIANT_EDGE[kind],
228
+ height: VARIANT_EDGE[kind] / 2,
229
+ }];
230
+ }
231
+ });
232
+ }); },
233
+ },
234
+ ];
235
+ };
236
+ /** The copy-for-others maker of the copy suite, in miniature: a picture's copy is its bytes with the mark taken off. */
237
+ var copyMakerCalls = [];
238
+ var registerCopyMaker = function () {
239
+ objectCache()['@proteinjs/db-file/FileCopyForOthers'] = [
240
+ {
241
+ appliesTo: function (file) { return file.type.startsWith('image/'); },
242
+ make: function (file, bytes) { return __awaiter(void 0, void 0, void 0, function () {
243
+ return __generator(this, function (_a) {
244
+ copyMakerCalls.push(file.id);
245
+ return [2 /*return*/, bytes.subarray(0, bytes.length - COPY_MARK.length)];
246
+ });
247
+ }); },
248
+ },
249
+ ];
250
+ };
251
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
252
+ return __generator(this, function (_a) {
253
+ switch (_a.label) {
254
+ case 0: return [4 /*yield*/, testEnv.beforeAll()];
255
+ case 1:
256
+ _a.sent();
257
+ objectCache()['@proteinjs/user-auth/AuthenticatedUserRepo'] = [new user_1.UserRepo()];
258
+ user_1.UserAuth.userRepo = undefined;
259
+ objectCache()['@proteinjs/db-file/FileReachabilityResolver'] = [
260
+ // The content leg vouches for the RECIPIENT alone (a share they accepted); a stranger holds no grant.
261
+ {
262
+ canReadViaReference: function (fileId) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
263
+ return [2 /*return*/, reachableFileIds.has(fileId) && new user_1.UserRepo().getUser().id === recipient.id];
264
+ }); }); },
265
+ },
266
+ ];
267
+ return [4 /*yield*/, testEnv.createUser({ name: 'File owner', email: 'variant-owner@test.local' })];
268
+ case 2:
269
+ owner = _a.sent();
270
+ return [4 /*yield*/, testEnv.createUser({ name: 'Share recipient', email: 'variant-recipient@test.local' })];
271
+ case 3:
272
+ recipient = _a.sent();
273
+ return [4 /*yield*/, testEnv.createUser({ name: 'Stranger', email: 'variant-stranger@test.local' })];
274
+ case 4:
275
+ stranger = _a.sent();
276
+ testEnv.actAs(owner);
277
+ return [2 /*return*/];
278
+ }
279
+ });
280
+ }); });
281
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
282
+ return __generator(this, function (_a) {
283
+ switch (_a.label) {
284
+ case 0:
285
+ delete objectCache()['@proteinjs/db-file/FileReachabilityResolver'];
286
+ delete objectCache()['@proteinjs/db-file/FileVariantMaker'];
287
+ delete objectCache()['@proteinjs/db-file/FileCopyForOthers'];
288
+ user_1.UserAuth.userRepo = undefined;
289
+ return [4 /*yield*/, testEnv.afterAll()];
290
+ case 1:
291
+ _a.sent();
292
+ return [2 /*return*/];
293
+ }
294
+ });
295
+ }); });
296
+ var driver = new ProxyDriver();
297
+ beforeEach(function () {
298
+ reachableFileIds.clear();
299
+ makerCalls = [];
300
+ copyMakerCalls = [];
301
+ kindsApplied = new Set(['preview', 'stage']);
302
+ makerBarrier = 0;
303
+ makerWaiting = [];
304
+ makerHold = undefined;
305
+ makerRefuses = false;
306
+ driver.reads = [];
307
+ driver.deletes = [];
308
+ registerVariantMaker();
309
+ objectCache()['@proteinjs/db-file/FileCopyForOthers'] = [];
310
+ testEnv.setDriver(driver);
311
+ testEnv.actAs(owner);
312
+ });
313
+ /** A picture of the owner's, stored with no variant (a file made before the seam, or by a producer that did not derive one). */
314
+ var createOwnerPicture = function (name, type, bytes) {
315
+ if (name === void 0) { name = 'photo.jpg'; }
316
+ if (type === void 0) { type = 'image/jpeg'; }
317
+ if (bytes === void 0) { bytes = originalBytes; }
318
+ return __awaiter(void 0, void 0, void 0, function () {
319
+ return __generator(this, function (_a) {
320
+ switch (_a.label) {
321
+ case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: name, type: type, size: bytes.length, width: 4032, height: 3024 }, bytes.toString('base64'))];
322
+ case 1: return [2 /*return*/, _a.sent()];
323
+ }
324
+ });
325
+ });
326
+ };
327
+ var rowAsSystem = function (fileId) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
328
+ switch (_a.label) {
329
+ case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(tables_1.tables.File, { id: fileId })];
330
+ case 1: return [2 /*return*/, _a.sent()];
331
+ }
332
+ }); }); };
333
+ var fileRowsInScope = function (scope) { return __awaiter(void 0, void 0, void 0, function () {
334
+ return __generator(this, function (_a) {
335
+ switch (_a.label) {
336
+ case 0: return [4 /*yield*/, (0, db_1.getDbAsSystem)().query(tables_1.tables.File, new db_1.QueryBuilderFactory().getQueryBuilder(tables_1.tables.File).condition({ field: 'scope', operator: '=', value: scope }))];
337
+ case 1: return [2 /*return*/, _a.sent()];
338
+ }
339
+ });
340
+ }); };
341
+ describe('the ingest door — deriveVariants with the bytes in hand', function () {
342
+ it("makes every kind the maker applies to, beside each other: two Files of the OWNER's, each naming its original, each named on the row", function () { return __awaiter(void 0, void 0, void 0, function () {
343
+ var file, derived, row, _i, _a, kind, variant;
344
+ var _b, _c, _d, _e, _f, _g, _h, _j, _k;
345
+ return __generator(this, function (_l) {
346
+ switch (_l.label) {
347
+ case 0: return [4 /*yield*/, createOwnerPicture('camera.jpg')];
348
+ case 1:
349
+ file = _l.sent();
350
+ return [4 /*yield*/, new FileStorage_1.FileStorage().deriveVariants(file, originalBytes)];
351
+ case 2:
352
+ derived = _l.sent();
353
+ expect(makerCalls.map(function (call) { return [call.fileId, call.kind]; })).toEqual([
354
+ [file.id, 'preview'],
355
+ [file.id, 'stage'],
356
+ ]);
357
+ expect(makerCalls.every(function (call) { return call.bytes.equals(originalBytes); })).toBe(true);
358
+ return [4 /*yield*/, rowAsSystem(file.id)];
359
+ case 3:
360
+ row = (_l.sent());
361
+ expect((_b = row.preview) === null || _b === void 0 ? void 0 : _b._id).toEqual(derived.variants.preview.id);
362
+ expect((_c = row.stage) === null || _c === void 0 ? void 0 : _c._id).toEqual(derived.variants.stage.id);
363
+ expect((_d = derived.file.preview) === null || _d === void 0 ? void 0 : _d._id).toEqual((_e = row.preview) === null || _e === void 0 ? void 0 : _e._id);
364
+ expect((_f = derived.file.stage) === null || _f === void 0 ? void 0 : _f._id).toEqual((_g = row.stage) === null || _g === void 0 ? void 0 : _g._id);
365
+ _i = 0, _a = ['preview', 'stage'];
366
+ _l.label = 4;
367
+ case 4:
368
+ if (!(_i < _a.length)) return [3 /*break*/, 7];
369
+ kind = _a[_i];
370
+ return [4 /*yield*/, rowAsSystem(derived.variants[kind].id)];
371
+ case 5:
372
+ variant = (_l.sent());
373
+ expect(variant.scope).toEqual(owner.id);
374
+ expect((_h = variant.variantOf) === null || _h === void 0 ? void 0 : _h._id).toEqual(file.id);
375
+ expect([variant.name, variant.type, variant.size, variant.width, variant.height]).toEqual([
376
+ "(".concat(kind, ") camera.jpg"),
377
+ 'image/webp',
378
+ variantOf(kind, originalBytes).length,
379
+ VARIANT_EDGE[kind],
380
+ VARIANT_EDGE[kind] / 2,
381
+ ]);
382
+ expect(driver.store.get(variant.id).equals(variantOf(kind, originalBytes))).toBe(true);
383
+ _l.label = 6;
384
+ case 6:
385
+ _i++;
386
+ return [3 /*break*/, 4];
387
+ case 7:
388
+ // The original is untouched: its bytes, its size, its own seat empty.
389
+ expect(driver.store.get(file.id).equals(originalBytes)).toBe(true);
390
+ expect((_k = (_j = row.variantOf) === null || _j === void 0 ? void 0 : _j._id) !== null && _k !== void 0 ? _k : null).toBeNull();
391
+ return [2 /*return*/];
392
+ }
393
+ });
394
+ }); });
395
+ it('a kind the maker does not apply to is not made and its seat stays empty; a kind already named is not made again; with no maker nothing is made', function () { return __awaiter(void 0, void 0, void 0, function () {
396
+ var file, first, _a, second, bare, none, _b;
397
+ var _c, _d, _e, _f, _g, _h;
398
+ return __generator(this, function (_j) {
399
+ switch (_j.label) {
400
+ case 0:
401
+ kindsApplied = new Set(['preview']);
402
+ return [4 /*yield*/, createOwnerPicture('small.png', 'image/png')];
403
+ case 1:
404
+ file = _j.sent();
405
+ return [4 /*yield*/, new FileStorage_1.FileStorage().deriveVariants(file, originalBytes)];
406
+ case 2:
407
+ first = _j.sent();
408
+ expect(Object.keys(first.variants)).toEqual(['preview']);
409
+ _a = expect;
410
+ return [4 /*yield*/, rowAsSystem(file.id)];
411
+ case 3:
412
+ _a.apply(void 0, [(_d = (_c = (_j.sent()).stage) === null || _c === void 0 ? void 0 : _c._id) !== null && _d !== void 0 ? _d : null]).toBeNull();
413
+ kindsApplied = new Set(['preview', 'stage']);
414
+ makerCalls = [];
415
+ return [4 /*yield*/, new FileStorage_1.FileStorage().deriveVariants(first.file, originalBytes)];
416
+ case 4:
417
+ second = _j.sent();
418
+ expect(makerCalls.map(function (call) { return call.kind; })).toEqual(['stage']);
419
+ expect((_e = second.file.preview) === null || _e === void 0 ? void 0 : _e._id).toEqual(first.variants.preview.id);
420
+ expect((_f = second.file.stage) === null || _f === void 0 ? void 0 : _f._id).toEqual(second.variants.stage.id);
421
+ objectCache()['@proteinjs/db-file/FileVariantMaker'] = [];
422
+ return [4 /*yield*/, createOwnerPicture('bare.jpg')];
423
+ case 5:
424
+ bare = _j.sent();
425
+ return [4 /*yield*/, new FileStorage_1.FileStorage().deriveVariants(bare, originalBytes)];
426
+ case 6:
427
+ none = _j.sent();
428
+ expect(none.variants).toEqual({});
429
+ _b = expect;
430
+ return [4 /*yield*/, rowAsSystem(bare.id)];
431
+ case 7:
432
+ _b.apply(void 0, [(_h = (_g = (_j.sent()).stage) === null || _g === void 0 ? void 0 : _g._id) !== null && _h !== void 0 ? _h : null]).toBeNull();
433
+ return [2 /*return*/];
434
+ }
435
+ });
436
+ }); });
437
+ });
438
+ describe('the lifecycle — the stage dies with its original and with its bytes, exactly as the preview does', function () {
439
+ it('deleting the original deletes the stage variant: its row and its bytes (the cascade)', function () { return __awaiter(void 0, void 0, void 0, function () {
440
+ var file, variants, _a, _b;
441
+ return __generator(this, function (_c) {
442
+ switch (_c.label) {
443
+ case 0: return [4 /*yield*/, createOwnerPicture('gone.jpg')];
444
+ case 1:
445
+ file = _c.sent();
446
+ return [4 /*yield*/, new FileStorage_1.FileStorage().deriveVariants(file, originalBytes)];
447
+ case 2:
448
+ variants = (_c.sent()).variants;
449
+ expect(driver.store.has(variants.stage.id)).toBe(true);
450
+ return [4 /*yield*/, new FileStorage_1.FileStorage().deleteFile(file.id)];
451
+ case 3:
452
+ _c.sent();
453
+ _a = expect;
454
+ return [4 /*yield*/, rowAsSystem(variants.stage.id)];
455
+ case 4:
456
+ _a.apply(void 0, [_c.sent()]).toBeUndefined();
457
+ expect(driver.store.has(variants.stage.id)).toBe(false);
458
+ _b = expect;
459
+ return [4 /*yield*/, rowAsSystem(variants.preview.id)];
460
+ case 5:
461
+ _b.apply(void 0, [_c.sent()]).toBeUndefined();
462
+ expect(driver.store.has(variants.preview.id)).toBe(false);
463
+ return [2 /*return*/];
464
+ }
465
+ });
466
+ }); });
467
+ it('new bytes drop every derived File — the preview, the stage and the copy for others: the row forgets them, their rows and bytes go', function () { return __awaiter(void 0, void 0, void 0, function () {
468
+ var file, variants, before, newBytes, after, _i, _a, id, _b, fresh;
469
+ var _c, _d, _e, _f, _g, _h, _j;
470
+ return __generator(this, function (_k) {
471
+ switch (_k.label) {
472
+ case 0:
473
+ registerCopyMaker();
474
+ return [4 /*yield*/, createOwnerPicture('rewritten.jpg')];
475
+ case 1:
476
+ file = _k.sent();
477
+ return [4 /*yield*/, new FileStorage_1.FileStorage().deriveVariants(file, originalBytes)];
478
+ case 2:
479
+ variants = (_k.sent()).variants;
480
+ reachableFileIds.add(file.id);
481
+ testEnv.actAs(recipient);
482
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(file.id)];
483
+ case 3:
484
+ _k.sent(); // the copy for others is made and named
485
+ return [4 /*yield*/, rowAsSystem(file.id)];
486
+ case 4:
487
+ before = (_k.sent());
488
+ expect((_c = before.copyForOthers) === null || _c === void 0 ? void 0 : _c._id).toBeTruthy();
489
+ testEnv.actAs(owner);
490
+ newBytes = Buffer.from('NEW-BYTES');
491
+ return [4 /*yield*/, new FileStorage_1.FileStorage().updateFileData(file.id, newBytes.toString('base64'))];
492
+ case 5:
493
+ _k.sent();
494
+ return [4 /*yield*/, rowAsSystem(file.id)];
495
+ case 6:
496
+ after = (_k.sent());
497
+ expect([(_e = (_d = after.preview) === null || _d === void 0 ? void 0 : _d._id) !== null && _e !== void 0 ? _e : null, (_g = (_f = after.stage) === null || _f === void 0 ? void 0 : _f._id) !== null && _g !== void 0 ? _g : null, (_j = (_h = after.copyForOthers) === null || _h === void 0 ? void 0 : _h._id) !== null && _j !== void 0 ? _j : null]).toEqual([
498
+ null,
499
+ null,
500
+ null,
501
+ ]);
502
+ _i = 0, _a = [variants.preview.id, variants.stage.id, before.copyForOthers._id];
503
+ _k.label = 7;
504
+ case 7:
505
+ if (!(_i < _a.length)) return [3 /*break*/, 10];
506
+ id = _a[_i];
507
+ _b = expect;
508
+ return [4 /*yield*/, rowAsSystem(id)];
509
+ case 8:
510
+ _b.apply(void 0, [_k.sent()]).toBeUndefined();
511
+ expect(driver.store.has(id)).toBe(false);
512
+ _k.label = 9;
513
+ case 9:
514
+ _i++;
515
+ return [3 /*break*/, 7];
516
+ case 10:
517
+ expect(driver.store.get(file.id).equals(newBytes)).toBe(true);
518
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
519
+ case 11:
520
+ fresh = (_k.sent());
521
+ expect(driver.store.get(fresh.id).equals(variantOf('stage', newBytes))).toBe(true);
522
+ return [2 /*return*/];
523
+ }
524
+ });
525
+ }); });
526
+ });
527
+ describe('the read path — a File without a stage derives it on the first request, once', function () {
528
+ it('the first getVariant makes the stage and names it; the second is served the same row without the maker', function () { return __awaiter(void 0, void 0, void 0, function () {
529
+ var file, _a, first, second, _b, _c;
530
+ var _d, _e, _f, _g, _h, _j;
531
+ return __generator(this, function (_k) {
532
+ switch (_k.label) {
533
+ case 0: return [4 /*yield*/, createOwnerPicture('existing.jpg')];
534
+ case 1:
535
+ file = _k.sent();
536
+ _a = expect;
537
+ return [4 /*yield*/, rowAsSystem(file.id)];
538
+ case 2:
539
+ _a.apply(void 0, [(_e = (_d = (_k.sent()).stage) === null || _d === void 0 ? void 0 : _d._id) !== null && _e !== void 0 ? _e : null]).toBeNull();
540
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
541
+ case 3:
542
+ first = (_k.sent());
543
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
544
+ case 4:
545
+ second = (_k.sent());
546
+ expect(makerCalls.map(function (call) { return call.kind; })).toEqual(['stage']);
547
+ expect(makerCalls[0].bytes.equals(originalBytes)).toBe(true);
548
+ expect(second.id).toEqual(first.id);
549
+ _b = expect;
550
+ return [4 /*yield*/, rowAsSystem(file.id)];
551
+ case 5:
552
+ _b.apply(void 0, [(_f = (_k.sent()).stage) === null || _f === void 0 ? void 0 : _f._id]).toEqual(first.id);
553
+ expect((_g = first.variantOf) === null || _g === void 0 ? void 0 : _g._id).toEqual(file.id);
554
+ expect([first.type, first.width, first.height]).toEqual(['image/webp', 1600, 800]);
555
+ expect(driver.store.get(first.id).equals(variantOf('stage', originalBytes))).toBe(true);
556
+ // Only the kind asked for: the preview seat stays empty.
557
+ _c = expect;
558
+ return [4 /*yield*/, rowAsSystem(file.id)];
559
+ case 6:
560
+ // Only the kind asked for: the preview seat stays empty.
561
+ _c.apply(void 0, [(_j = (_h = (_k.sent()).preview) === null || _h === void 0 ? void 0 : _h._id) !== null && _j !== void 0 ? _j : null]).toBeNull();
562
+ return [2 /*return*/];
563
+ }
564
+ });
565
+ }); });
566
+ it("a recipient's read derives it as the OWNER's File; the owner is then served the same row", function () { return __awaiter(void 0, void 0, void 0, function () {
567
+ var file, theirs, mine, _a;
568
+ return __generator(this, function (_b) {
569
+ switch (_b.label) {
570
+ case 0: return [4 /*yield*/, createOwnerPicture('shared.jpg')];
571
+ case 1:
572
+ file = _b.sent();
573
+ reachableFileIds.add(file.id);
574
+ testEnv.actAs(recipient);
575
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
576
+ case 2:
577
+ theirs = (_b.sent());
578
+ testEnv.actAs(owner);
579
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
580
+ case 3:
581
+ mine = (_b.sent());
582
+ expect(theirs.scope).toEqual(owner.id);
583
+ expect(mine.id).toEqual(theirs.id);
584
+ expect(makerCalls).toHaveLength(1);
585
+ _a = expect;
586
+ return [4 /*yield*/, fileRowsInScope(recipient.id)];
587
+ case 4:
588
+ _a.apply(void 0, [(_b.sent()).map(function (row) { return row.id; })]).toEqual([]);
589
+ return [2 /*return*/];
590
+ }
591
+ });
592
+ }); });
593
+ it('a kind the maker does not apply to makes nothing and answers undefined; a caller who cannot read the original gets undefined and nothing is made', function () { return __awaiter(void 0, void 0, void 0, function () {
594
+ var clip, _a, _b, photo, _c, _d;
595
+ var _e, _f, _g, _h;
596
+ return __generator(this, function (_j) {
597
+ switch (_j.label) {
598
+ case 0: return [4 /*yield*/, createOwnerPicture('clip.mp4', 'video/mp4')];
599
+ case 1:
600
+ clip = _j.sent();
601
+ _a = expect;
602
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(clip.id, 'stage')];
603
+ case 2:
604
+ _a.apply(void 0, [_j.sent()]).toBeUndefined();
605
+ _b = expect;
606
+ return [4 /*yield*/, rowAsSystem(clip.id)];
607
+ case 3:
608
+ _b.apply(void 0, [(_f = (_e = (_j.sent()).stage) === null || _e === void 0 ? void 0 : _e._id) !== null && _f !== void 0 ? _f : null]).toBeNull();
609
+ return [4 /*yield*/, createOwnerPicture('private.jpg')];
610
+ case 4:
611
+ photo = _j.sent();
612
+ testEnv.actAs(stranger);
613
+ _c = expect;
614
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(photo.id, 'stage')];
615
+ case 5:
616
+ _c.apply(void 0, [_j.sent()]).toBeUndefined();
617
+ expect(makerCalls).toEqual([]);
618
+ _d = expect;
619
+ return [4 /*yield*/, rowAsSystem(photo.id)];
620
+ case 6:
621
+ _d.apply(void 0, [(_h = (_g = (_j.sent()).stage) === null || _g === void 0 ? void 0 : _g._id) !== null && _h !== void 0 ? _h : null]).toBeNull();
622
+ return [2 /*return*/];
623
+ }
624
+ });
625
+ }); });
626
+ it('two PROCESSES deriving at once (the row is the only thing they share): the row names one stage, the other is deleted with its bytes', function () { return __awaiter(void 0, void 0, void 0, function () {
627
+ var file, storedBefore, storage, _a, a, b, named, stagesOfFile;
628
+ return __generator(this, function (_b) {
629
+ switch (_b.label) {
630
+ case 0: return [4 /*yield*/, createOwnerPicture('raced.jpg')];
631
+ case 1:
632
+ file = _b.sent();
633
+ storedBefore = driver.store.size;
634
+ makerBarrier = 2;
635
+ storage = new FileStorage_1.FileStorage();
636
+ return [4 /*yield*/, Promise.all([
637
+ storage.makeVariant(file, 'stage', originalBytes),
638
+ storage.makeVariant(file, 'stage', originalBytes),
639
+ ])];
640
+ case 2:
641
+ _a = _b.sent(), a = _a[0], b = _a[1];
642
+ expect(a).toEqual(b);
643
+ return [4 /*yield*/, rowAsSystem(file.id)];
644
+ case 3:
645
+ named = (_b.sent()).stage._id;
646
+ expect(named).toEqual(a);
647
+ return [4 /*yield*/, fileRowsInScope(owner.id)];
648
+ case 4:
649
+ stagesOfFile = (_b.sent()).filter(function (row) { var _a; return ((_a = row.variantOf) === null || _a === void 0 ? void 0 : _a._id) === file.id; });
650
+ expect(stagesOfFile.map(function (row) { return row.id; })).toEqual([named]);
651
+ // One stage's bytes remain of the two made: the loser's row went, and its bytes with it.
652
+ expect(driver.store.size).toEqual(storedBefore + 1);
653
+ expect(driver.store.has(named)).toBe(true);
654
+ return [2 /*return*/];
655
+ }
656
+ });
657
+ }); });
658
+ it('N readers asking one process for the same absent stage while it derives cost ONE derivation: one read of the original, one maker call, one object stored, no loser to delete; every reader is served the same row', function () { return __awaiter(void 0, void 0, void 0, function () {
659
+ var file, storedBefore, readsBefore, release, readers, served, named, _a;
660
+ return __generator(this, function (_b) {
661
+ switch (_b.label) {
662
+ case 0: return [4 /*yield*/, createOwnerPicture('burst.jpg')];
663
+ case 1:
664
+ file = _b.sent();
665
+ storedBefore = driver.store.size;
666
+ readsBefore = driver.reads.filter(function (id) { return id === file.id; }).length;
667
+ makerHold = new Promise(function (resolve) { return (release = resolve); });
668
+ readers = Array.from({ length: 4 }, function () { return new FileStorage_1.FileStorage().getVariant(file.id, 'stage'); });
669
+ _b.label = 2;
670
+ case 2:
671
+ if (!(makerCalls.length < 1)) return [3 /*break*/, 4];
672
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
673
+ case 3:
674
+ _b.sent();
675
+ return [3 /*break*/, 2];
676
+ case 4: return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
677
+ case 5:
678
+ _b.sent();
679
+ release();
680
+ return [4 /*yield*/, Promise.all(readers)];
681
+ case 6:
682
+ served = _b.sent();
683
+ expect(makerCalls).toHaveLength(1);
684
+ expect(driver.reads.filter(function (id) { return id === file.id; }).length).toEqual(readsBefore + 1);
685
+ return [4 /*yield*/, rowAsSystem(file.id)];
686
+ case 7:
687
+ named = (_b.sent()).stage._id;
688
+ expect(served.map(function (row) { return row.id; })).toEqual([named, named, named, named]);
689
+ expect(driver.store.size).toEqual(storedBefore + 1);
690
+ expect(driver.deletes).toEqual([]);
691
+ // The next reader, after the derivation, is served the row without the maker.
692
+ _a = expect;
693
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
694
+ case 8:
695
+ // The next reader, after the derivation, is served the row without the maker.
696
+ _a.apply(void 0, [(_b.sent()).id]).toEqual(named);
697
+ expect(makerCalls).toHaveLength(1);
698
+ return [2 /*return*/];
699
+ }
700
+ });
701
+ }); });
702
+ it('a maker that cannot make the stage of these bytes on the read path: nothing is stored, the reader is answered undefined (the file itself is drawn), the next reader asks the maker again', function () { return __awaiter(void 0, void 0, void 0, function () {
703
+ var file, _a, _b, _c, served;
704
+ var _d, _e;
705
+ return __generator(this, function (_f) {
706
+ switch (_f.label) {
707
+ case 0: return [4 /*yield*/, createOwnerPicture('unreadable.jpg')];
708
+ case 1:
709
+ file = _f.sent();
710
+ makerRefuses = true;
711
+ _a = expect;
712
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
713
+ case 2:
714
+ _a.apply(void 0, [_f.sent()]).toBeUndefined();
715
+ expect(makerCalls).toHaveLength(1);
716
+ _b = expect;
717
+ return [4 /*yield*/, rowAsSystem(file.id)];
718
+ case 3:
719
+ _b.apply(void 0, [(_e = (_d = (_f.sent()).stage) === null || _d === void 0 ? void 0 : _d._id) !== null && _e !== void 0 ? _e : null]).toBeNull();
720
+ _c = expect;
721
+ return [4 /*yield*/, fileRowsInScope(owner.id)];
722
+ case 4:
723
+ _c.apply(void 0, [(_f.sent()).filter(function (row) { var _a; return ((_a = row.variantOf) === null || _a === void 0 ? void 0 : _a._id) === file.id; })]).toEqual([]);
724
+ return [4 /*yield*/, invokeVariantRoute(file.id, 'stage')];
725
+ case 5:
726
+ served = _f.sent();
727
+ expect(served.statusCode).toBeUndefined();
728
+ expect(served.body.equals(originalBytes)).toBe(true);
729
+ expect(served.headers['content-type']).toEqual('image/jpeg');
730
+ expect(makerCalls).toHaveLength(2);
731
+ // The ingest door is not softened: the maker's failure reaches the caller that holds the bytes.
732
+ return [4 /*yield*/, expect(new FileStorage_1.FileStorage().deriveVariants(file, originalBytes)).rejects.toThrow('cannot decode')];
733
+ case 6:
734
+ // The ingest door is not softened: the maker's failure reaches the caller that holds the bytes.
735
+ _f.sent();
736
+ return [2 /*return*/];
737
+ }
738
+ });
739
+ }); });
740
+ });
741
+ describe('access — a variant is readable by whoever can read its original, and served like any File', function () {
742
+ it('a stage derived after the content was placed is reachable by its own id through its original: the recipient reads it, a stranger cannot', function () { return __awaiter(void 0, void 0, void 0, function () {
743
+ var file, stage, _a, _b, _c, _d, _e, _f, _g, _h;
744
+ var _j;
745
+ return __generator(this, function (_k) {
746
+ switch (_k.label) {
747
+ case 0: return [4 /*yield*/, createOwnerPicture('placed.jpg')];
748
+ case 1:
749
+ file = _k.sent();
750
+ reachableFileIds.add(file.id); // the content row names the ORIGINAL only — nothing names the stage
751
+ testEnv.actAs(recipient);
752
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
753
+ case 2:
754
+ stage = (_k.sent());
755
+ _a = expect;
756
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFile(stage.id)];
757
+ case 3:
758
+ _a.apply(void 0, [(_j = (_k.sent())) === null || _j === void 0 ? void 0 : _j.id]).toEqual(stage.id);
759
+ _b = expect;
760
+ return [4 /*yield*/, invokeRoute(stage.id)];
761
+ case 4:
762
+ _b.apply(void 0, [(_k.sent()).statusCode]).toBeUndefined(); // a plain 200 send: no status set
763
+ _c = expect;
764
+ _e = (_d = Buffer).isBuffer;
765
+ return [4 /*yield*/, invokeRoute(stage.id)];
766
+ case 5:
767
+ _c.apply(void 0, [_e.apply(_d, [(_k.sent()).body])]).toBe(true);
768
+ testEnv.actAs(stranger);
769
+ _f = expect;
770
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFile(stage.id)];
771
+ case 6:
772
+ _f.apply(void 0, [_k.sent()]).toBeUndefined();
773
+ _g = expect;
774
+ return [4 /*yield*/, invokeRoute(stage.id)];
775
+ case 7:
776
+ _g.apply(void 0, [(_k.sent()).statusCode]).toEqual(404);
777
+ // Revoked: the moment the original is out of reach, so is its variant.
778
+ reachableFileIds.delete(file.id);
779
+ testEnv.actAs(recipient);
780
+ _h = expect;
781
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFile(stage.id)];
782
+ case 8:
783
+ _h.apply(void 0, [_k.sent()]).toBeUndefined();
784
+ return [2 /*return*/];
785
+ }
786
+ });
787
+ }); });
788
+ it('the non-owner copy rule applies to the stage automatically: a recipient is served the copy of the STAGE, made once and named on the stage row; the owner the stage itself', function () { return __awaiter(void 0, void 0, void 0, function () {
789
+ var file, stage, stageBytes, served, _a, _b, routed, again, _c, _d, ownersOwn, _e, _f, stageRow, copy, _g;
790
+ var _h, _j, _k;
791
+ return __generator(this, function (_l) {
792
+ switch (_l.label) {
793
+ case 0:
794
+ registerCopyMaker();
795
+ return [4 /*yield*/, createOwnerPicture('located.jpg', 'image/jpeg', Buffer.concat([originalBytes, COPY_MARK]))];
796
+ case 1:
797
+ file = _l.sent();
798
+ reachableFileIds.add(file.id);
799
+ // The stub variant maker keeps the mark on what it makes (the real one keeps a picture's tags for its owner).
800
+ objectCache()['@proteinjs/db-file/FileVariantMaker'] = [
801
+ {
802
+ appliesTo: function (candidate, kind) { return candidate.type.startsWith('image/') && kind === 'stage'; },
803
+ make: function (candidate, bytes) { return __awaiter(void 0, void 0, void 0, function () {
804
+ return __generator(this, function (_a) {
805
+ return [2 /*return*/, ({
806
+ bytes: Buffer.concat([Buffer.from('STAGE:'), bytes.subarray(0, 8), COPY_MARK]),
807
+ type: 'image/webp',
808
+ })];
809
+ });
810
+ }); },
811
+ },
812
+ ];
813
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getVariant(file.id, 'stage')];
814
+ case 2:
815
+ stage = (_l.sent());
816
+ stageBytes = driver.store.get(stage.id);
817
+ expect(stageBytes.includes(COPY_MARK)).toBe(true);
818
+ testEnv.actAs(recipient);
819
+ _b = (_a = Buffer).from;
820
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(stage.id)];
821
+ case 3:
822
+ served = _b.apply(_a, [_l.sent(), 'base64']);
823
+ return [4 /*yield*/, invokeRoute(stage.id)];
824
+ case 4:
825
+ routed = (_l.sent()).body;
826
+ _d = (_c = Buffer).from;
827
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(stage.id)];
828
+ case 5:
829
+ again = _d.apply(_c, [_l.sent(), 'base64']);
830
+ testEnv.actAs(owner);
831
+ _f = (_e = Buffer).from;
832
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFileData(stage.id)];
833
+ case 6:
834
+ ownersOwn = _f.apply(_e, [_l.sent(), 'base64']);
835
+ expect(served.includes(COPY_MARK)).toBe(false);
836
+ expect(served.equals(stageBytes.subarray(0, stageBytes.length - COPY_MARK.length))).toBe(true);
837
+ expect(routed.equals(served)).toBe(true);
838
+ expect(again.equals(served)).toBe(true);
839
+ expect(copyMakerCalls).toEqual([stage.id]);
840
+ return [4 /*yield*/, rowAsSystem(stage.id)];
841
+ case 7:
842
+ stageRow = (_l.sent());
843
+ expect((_h = stageRow.copyForOthers) === null || _h === void 0 ? void 0 : _h._id).toBeTruthy();
844
+ return [4 /*yield*/, rowAsSystem(stageRow.copyForOthers._id)];
845
+ case 8:
846
+ copy = (_l.sent());
847
+ expect(copy.scope).toEqual(owner.id);
848
+ expect((_k = (_j = copy.variantOf) === null || _j === void 0 ? void 0 : _j._id) !== null && _k !== void 0 ? _k : null).toBeNull(); // the copy's own id stays nobody's but the owner's
849
+ testEnv.actAs(recipient);
850
+ _g = expect;
851
+ return [4 /*yield*/, new FileStorage_1.FileStorage().getFile(copy.id)];
852
+ case 9:
853
+ _g.apply(void 0, [_l.sent()]).toBeUndefined();
854
+ expect(ownersOwn.equals(stageBytes)).toBe(true);
855
+ return [2 /*return*/];
856
+ }
857
+ });
858
+ }); });
859
+ });
860
+ describe('the route — GET /file/:id/variant/:kind is the one URL a surface asks a variant by (an existing row derives on the first request)', function () {
861
+ it('names its path as the file route does', function () {
862
+ expect(getFileVariantRoute_1.getFileVariantRoute.path('abc', 'stage')).toEqual('/file/abc/variant/stage');
863
+ expect(getFileVariant_1.getFileVariant.path).toEqual('/file/:id/variant/:kind');
864
+ });
865
+ it("the first request derives the stage and serves ITS bytes with ITS headers; the second is served the same row without the maker; the original's own route is untouched", function () { return __awaiter(void 0, void 0, void 0, function () {
866
+ var file, first, second, stageId, _i, _a, served, _b, original;
867
+ var _c;
868
+ return __generator(this, function (_d) {
869
+ switch (_d.label) {
870
+ case 0: return [4 /*yield*/, createOwnerPicture('old-row.jpg')];
871
+ case 1:
872
+ file = _d.sent();
873
+ return [4 /*yield*/, invokeVariantRoute(file.id, 'stage')];
874
+ case 2:
875
+ first = _d.sent();
876
+ return [4 /*yield*/, invokeVariantRoute(file.id, 'stage')];
877
+ case 3:
878
+ second = _d.sent();
879
+ expect(makerCalls.map(function (call) { return call.kind; })).toEqual(['stage']);
880
+ return [4 /*yield*/, rowAsSystem(file.id)];
881
+ case 4:
882
+ stageId = (_d.sent()).stage._id;
883
+ for (_i = 0, _a = [first, second]; _i < _a.length; _i++) {
884
+ served = _a[_i];
885
+ expect(served.statusCode).toBeUndefined();
886
+ expect(served.body.equals(variantOf('stage', originalBytes))).toBe(true);
887
+ expect(served.headers['content-type']).toEqual('image/webp');
888
+ expect(served.headers['content-disposition']).toEqual("inline; filename=\"".concat(encodeURIComponent("(stage) old-row.jpg"), "\""));
889
+ }
890
+ _b = expect;
891
+ return [4 /*yield*/, rowAsSystem(stageId)];
892
+ case 5:
893
+ _b.apply(void 0, [(_c = (_d.sent()).variantOf) === null || _c === void 0 ? void 0 : _c._id]).toEqual(file.id);
894
+ return [4 /*yield*/, invokeRoute(file.id)];
895
+ case 6:
896
+ original = _d.sent();
897
+ expect(original.body.equals(originalBytes)).toBe(true);
898
+ expect(original.headers['content-type']).toEqual('image/jpeg');
899
+ return [2 /*return*/];
900
+ }
901
+ });
902
+ }); });
903
+ it('a file the maker does not apply to is served as ITSELF through the variant route (the consumer always gets a picture); nothing is made', function () { return __awaiter(void 0, void 0, void 0, function () {
904
+ var clip, served, _a;
905
+ var _b, _c;
906
+ return __generator(this, function (_d) {
907
+ switch (_d.label) {
908
+ case 0: return [4 /*yield*/, createOwnerPicture('clip.mp4', 'video/mp4')];
909
+ case 1:
910
+ clip = _d.sent();
911
+ return [4 /*yield*/, invokeVariantRoute(clip.id, 'stage')];
912
+ case 2:
913
+ served = _d.sent();
914
+ expect(served.statusCode).toBeUndefined();
915
+ expect(served.body.equals(originalBytes)).toBe(true);
916
+ expect(served.headers['content-type']).toEqual('video/mp4');
917
+ expect(makerCalls).toEqual([]);
918
+ _a = expect;
919
+ return [4 /*yield*/, rowAsSystem(clip.id)];
920
+ case 3:
921
+ _a.apply(void 0, [(_c = (_b = (_d.sent()).stage) === null || _b === void 0 ? void 0 : _b._id) !== null && _c !== void 0 ? _c : null]).toBeNull();
922
+ return [2 /*return*/];
923
+ }
924
+ });
925
+ }); });
926
+ it('a kind the library does not know, a file the caller cannot read, and no session each answer without a byte', function () { return __awaiter(void 0, void 0, void 0, function () {
927
+ var photo, _a, _b, _c, _d;
928
+ var _e, _f;
929
+ return __generator(this, function (_g) {
930
+ switch (_g.label) {
931
+ case 0: return [4 /*yield*/, createOwnerPicture('private.jpg')];
932
+ case 1:
933
+ photo = _g.sent();
934
+ _a = expect;
935
+ return [4 /*yield*/, invokeVariantRoute(photo.id, 'poster')];
936
+ case 2:
937
+ _a.apply(void 0, [(_g.sent()).statusCode]).toEqual(404);
938
+ expect(makerCalls).toEqual([]);
939
+ testEnv.actAs(stranger);
940
+ _b = expect;
941
+ return [4 /*yield*/, invokeVariantRoute(photo.id, 'stage')];
942
+ case 3:
943
+ _b.apply(void 0, [(_g.sent()).statusCode]).toEqual(404);
944
+ expect(makerCalls).toEqual([]);
945
+ _c = expect;
946
+ return [4 /*yield*/, rowAsSystem(photo.id)];
947
+ case 4:
948
+ _c.apply(void 0, [(_f = (_e = (_g.sent()).stage) === null || _e === void 0 ? void 0 : _e._id) !== null && _f !== void 0 ? _f : null]).toBeNull();
949
+ testEnv.actAs(user_1.guestUser);
950
+ _d = expect;
951
+ return [4 /*yield*/, invokeVariantRoute(photo.id, 'stage')];
952
+ case 5:
953
+ _d.apply(void 0, [(_g.sent()).statusCode]).toEqual(401);
954
+ return [2 /*return*/];
955
+ }
956
+ });
957
+ }); });
958
+ it("a recipient's request through the route derives the stage as the OWNER's File and is served the COPY of it (the non-owner rule, on the route); the owner the stage itself", function () { return __awaiter(void 0, void 0, void 0, function () {
959
+ var file, theirs, mine, stageRow, _a, _b;
960
+ return __generator(this, function (_c) {
961
+ switch (_c.label) {
962
+ case 0:
963
+ registerCopyMaker();
964
+ return [4 /*yield*/, createOwnerPicture('shared-old.jpg', 'image/jpeg', Buffer.concat([originalBytes, COPY_MARK]))];
965
+ case 1:
966
+ file = _c.sent();
967
+ reachableFileIds.add(file.id);
968
+ objectCache()['@proteinjs/db-file/FileVariantMaker'] = [
969
+ {
970
+ appliesTo: function (candidate, kind) { return candidate.type.startsWith('image/') && kind === 'stage'; },
971
+ make: function (candidate, bytes) { return __awaiter(void 0, void 0, void 0, function () {
972
+ return __generator(this, function (_a) {
973
+ return [2 /*return*/, ({
974
+ bytes: Buffer.concat([Buffer.from('STAGE:'), bytes.subarray(0, 8), COPY_MARK]),
975
+ type: 'image/webp',
976
+ })];
977
+ });
978
+ }); },
979
+ },
980
+ ];
981
+ testEnv.actAs(recipient);
982
+ return [4 /*yield*/, invokeVariantRoute(file.id, 'stage')];
983
+ case 2:
984
+ theirs = _c.sent();
985
+ testEnv.actAs(owner);
986
+ return [4 /*yield*/, invokeVariantRoute(file.id, 'stage')];
987
+ case 3:
988
+ mine = _c.sent();
989
+ _a = rowAsSystem;
990
+ return [4 /*yield*/, rowAsSystem(file.id)];
991
+ case 4: return [4 /*yield*/, _a.apply(void 0, [(_c.sent()).stage._id])];
992
+ case 5:
993
+ stageRow = (_c.sent());
994
+ expect(stageRow.scope).toEqual(owner.id);
995
+ expect(theirs.body.includes(COPY_MARK)).toBe(false);
996
+ expect(mine.body.includes(COPY_MARK)).toBe(true);
997
+ expect(mine.body.equals(driver.store.get(stageRow.id))).toBe(true);
998
+ expect(copyMakerCalls).toEqual([stageRow.id]);
999
+ _b = expect;
1000
+ return [4 /*yield*/, fileRowsInScope(recipient.id)];
1001
+ case 6:
1002
+ _b.apply(void 0, [(_c.sent()).map(function (row) { return row.id; })]).toEqual([]);
1003
+ return [2 /*return*/];
1004
+ }
1005
+ });
1006
+ }); });
1007
+ });
1008
+ //# sourceMappingURL=FileVariants.test.js.map