@proteinjs/db-file 1.4.14 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/generated/index.js +3 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/DbFileStorageDriver.d.ts +2 -1
- package/dist/src/DbFileStorageDriver.d.ts.map +1 -1
- package/dist/src/DbFileStorageDriver.js +23 -7
- package/dist/src/DbFileStorageDriver.js.map +1 -1
- package/dist/src/FileStorage.d.ts +24 -6
- package/dist/src/FileStorage.d.ts.map +1 -1
- package/dist/src/FileStorage.js +57 -22
- package/dist/src/FileStorage.js.map +1 -1
- package/dist/src/FileStorageDriver.d.ts +26 -1
- package/dist/src/FileStorageDriver.d.ts.map +1 -1
- package/dist/src/FileStorageTableWatcher.d.ts +18 -0
- package/dist/src/FileStorageTableWatcher.d.ts.map +1 -0
- package/dist/src/FileStorageTableWatcher.js +96 -0
- package/dist/src/FileStorageTableWatcher.js.map +1 -0
- package/dist/src/routes/getFile.d.ts +11 -0
- package/dist/src/routes/getFile.d.ts.map +1 -1
- package/dist/src/routes/getFile.js +31 -15
- package/dist/src/routes/getFile.js.map +1 -1
- package/dist/src/tables/FileTable.d.ts +12 -4
- package/dist/src/tables/FileTable.d.ts.map +1 -1
- package/dist/src/tables/FileTable.js +5 -7
- package/dist/src/tables/FileTable.js.map +1 -1
- package/dist/test/FileMediaMetadata.test.d.ts +2 -0
- package/dist/test/FileMediaMetadata.test.d.ts.map +1 -0
- package/dist/test/FileMediaMetadata.test.js +154 -0
- package/dist/test/FileMediaMetadata.test.js.map +1 -0
- package/dist/test/FileStorageDeleteFile.test.d.ts +2 -0
- package/dist/test/FileStorageDeleteFile.test.d.ts.map +1 -0
- package/dist/test/FileStorageDeleteFile.test.js +259 -0
- package/dist/test/FileStorageDeleteFile.test.js.map +1 -0
- package/dist/test/FileTestEnvironment.d.ts +33 -0
- package/dist/test/FileTestEnvironment.d.ts.map +1 -0
- package/dist/test/FileTestEnvironment.js +229 -0
- package/dist/test/FileTestEnvironment.js.map +1 -0
- package/dist/test/GetFileRoute.test.d.ts +2 -0
- package/dist/test/GetFileRoute.test.d.ts.map +1 -0
- package/dist/test/GetFileRoute.test.js +432 -0
- package/dist/test/GetFileRoute.test.js.map +1 -0
- package/generated/index.ts +3 -1
- package/index.ts +1 -0
- package/jest.config.js +4 -0
- package/package.json +5 -3
- package/src/DbFileStorageDriver.ts +18 -8
- package/src/FileStorage.ts +48 -23
- package/src/FileStorageDriver.ts +24 -1
- package/src/FileStorageTableWatcher.ts +43 -0
- package/src/routes/getFile.ts +26 -8
- package/src/tables/FileTable.ts +14 -7
- package/test/FileMediaMetadata.test.ts +74 -0
- package/test/FileStorageDeleteFile.test.ts +128 -0
- package/test/FileTestEnvironment.ts +118 -0
- package/test/GetFileRoute.test.ts +258 -0
- package/test/emulatorLock.globalSetup.js +11 -0
- package/test/emulatorLock.globalTeardown.js +8 -0
- package/test/emulatorLock.js +124 -0
- package/test/setup.js +2 -0
|
@@ -0,0 +1,96 @@
|
|
|
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.FileStorageTableWatcher = void 0;
|
|
40
|
+
var logger_1 = require("@proteinjs/logger");
|
|
41
|
+
var FileTable_1 = require("./tables/FileTable");
|
|
42
|
+
var FileStorage_1 = require("./FileStorage");
|
|
43
|
+
/**
|
|
44
|
+
* Deletes stored file bytes whenever file rows are deleted. Lives at the Db layer so every
|
|
45
|
+
* delete path reaches the byte store — `FileStorage.deleteFile`, reference cascades (e.g.
|
|
46
|
+
* `file.preview`), and system sweeps that delete file rows directly.
|
|
47
|
+
*
|
|
48
|
+
* Bytes go first (`beforeDelete`): if the byte delete fails, the row survives to drive a retry;
|
|
49
|
+
* the reverse order would strand bytes with no row left to retry from. A retried delete is safe
|
|
50
|
+
* because `FileStorageDriver.deleteFile` is idempotent on the goal state.
|
|
51
|
+
*/
|
|
52
|
+
var FileStorageTableWatcher = /** @class */ (function () {
|
|
53
|
+
function FileStorageTableWatcher() {
|
|
54
|
+
this.logger = new logger_1.Logger({ name: this.constructor.name });
|
|
55
|
+
}
|
|
56
|
+
FileStorageTableWatcher.prototype.name = function () {
|
|
57
|
+
return this.constructor.name;
|
|
58
|
+
};
|
|
59
|
+
FileStorageTableWatcher.prototype.table = function () {
|
|
60
|
+
return new FileTable_1.FileTable();
|
|
61
|
+
};
|
|
62
|
+
FileStorageTableWatcher.prototype.beforeDelete = function (recordsToDelete, initialQb, deleteQb) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
64
|
+
var driver, deletedFileIds, _i, recordsToDelete_1, record;
|
|
65
|
+
return __generator(this, function (_a) {
|
|
66
|
+
switch (_a.label) {
|
|
67
|
+
case 0:
|
|
68
|
+
driver = FileStorage_1.FileStorage.getDriver();
|
|
69
|
+
deletedFileIds = [];
|
|
70
|
+
_i = 0, recordsToDelete_1 = recordsToDelete;
|
|
71
|
+
_a.label = 1;
|
|
72
|
+
case 1:
|
|
73
|
+
if (!(_i < recordsToDelete_1.length)) return [3 /*break*/, 4];
|
|
74
|
+
record = recordsToDelete_1[_i];
|
|
75
|
+
return [4 /*yield*/, driver.deleteFile(record.id)];
|
|
76
|
+
case 2:
|
|
77
|
+
_a.sent();
|
|
78
|
+
deletedFileIds.push(record.id);
|
|
79
|
+
_a.label = 3;
|
|
80
|
+
case 3:
|
|
81
|
+
_i++;
|
|
82
|
+
return [3 /*break*/, 1];
|
|
83
|
+
case 4:
|
|
84
|
+
this.logger.info({
|
|
85
|
+
message: "Deleted stored bytes for file records being deleted",
|
|
86
|
+
obj: { deletedFileIds: deletedFileIds },
|
|
87
|
+
});
|
|
88
|
+
return [2 /*return*/];
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
return FileStorageTableWatcher;
|
|
94
|
+
}());
|
|
95
|
+
exports.FileStorageTableWatcher = FileStorageTableWatcher;
|
|
96
|
+
//# sourceMappingURL=FileStorageTableWatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileStorageTableWatcher.js","sourceRoot":"","sources":["../../src/FileStorageTableWatcher.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,4CAA2C;AAC3C,gDAAqD;AACrD,6CAA4C;AAE5C;;;;;;;;GAQG;AACH;IAAA;QACU,WAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IA2B/D,CAAC;IAzBC,sCAAI,GAAJ;QACE,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED,uCAAK,GAAL;QACE,OAAO,IAAI,qBAAS,EAAE,CAAC;IACzB,CAAC;IAEK,8CAAY,GAAlB,UACE,eAAoB,EACpB,SAA0B,EAC1B,QAAyB;;;;;;wBAEnB,MAAM,GAAG,yBAAW,CAAC,SAAS,EAAE,CAAC;wBACjC,cAAc,GAAa,EAAE,CAAC;8BACA,EAAf,mCAAe;;;6BAAf,CAAA,6BAAe,CAAA;wBAAzB,MAAM;wBACf,qBAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,EAAA;;wBAAlC,SAAkC,CAAC;wBACnC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;;;wBAFZ,IAAe,CAAA;;;wBAKpC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;4BACf,OAAO,EAAE,qDAAqD;4BAC9D,GAAG,EAAE,EAAE,cAAc,gBAAA,EAAE;yBACxB,CAAC,CAAC;;;;;KACJ;IACH,8BAAC;AAAD,CAAC,AA5BD,IA4BC;AA5BY,0DAAuB"}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
import { Route } from '@proteinjs/server-api';
|
|
2
|
+
/**
|
|
3
|
+
* Serves a file's bytes. Auth first (logged-in + scoped row read), then one of two paths:
|
|
4
|
+
*
|
|
5
|
+
* - **302 redirect** to a short-lived signed URL when the driver's store has an external URL
|
|
6
|
+
* space (GCS). The app URL stays the one stable reference every `<img>`/`<video>`/chip uses;
|
|
7
|
+
* the client follows the redirect and reads the store directly — native Range/206 for video
|
|
8
|
+
* seeking, real caching, bytes never transit the app server. The redirect itself is cached
|
|
9
|
+
* briefly (client-private, well under the signed TTL) so repeated loads reuse one URL.
|
|
10
|
+
* - **Proxy** for drivers with no external URL space (`DbFileStorageDriver`): the interface's
|
|
11
|
+
* base64 is decoded so every mime — binary or text — serves its true bytes.
|
|
12
|
+
*/
|
|
2
13
|
export declare const getFile: Route;
|
|
3
14
|
//# sourceMappingURL=getFile.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAI9C,eAAO,MAAM,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"getFile.d.ts","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAI9C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,EAAE,KAoCrB,CAAC"}
|
|
@@ -39,11 +39,22 @@ 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
|
+
/**
|
|
43
|
+
* Serves a file's bytes. Auth first (logged-in + scoped row read), then one of two paths:
|
|
44
|
+
*
|
|
45
|
+
* - **302 redirect** to a short-lived signed URL when the driver's store has an external URL
|
|
46
|
+
* space (GCS). The app URL stays the one stable reference every `<img>`/`<video>`/chip uses;
|
|
47
|
+
* the client follows the redirect and reads the store directly — native Range/206 for video
|
|
48
|
+
* seeking, real caching, bytes never transit the app server. The redirect itself is cached
|
|
49
|
+
* briefly (client-private, well under the signed TTL) so repeated loads reuse one URL.
|
|
50
|
+
* - **Proxy** for drivers with no external URL space (`DbFileStorageDriver`): the interface's
|
|
51
|
+
* base64 is decoded so every mime — binary or text — serves its true bytes.
|
|
52
|
+
*/
|
|
42
53
|
exports.getFile = {
|
|
43
54
|
path: '/file/:id',
|
|
44
55
|
method: 'get',
|
|
45
56
|
onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
|
|
46
|
-
var fileId, fileStorage, file, fileDataBase64, safeFilename,
|
|
57
|
+
var fileId, fileStorage, file, signedUrl, fileDataBase64, safeFilename, error_1;
|
|
47
58
|
return __generator(this, function (_a) {
|
|
48
59
|
switch (_a.label) {
|
|
49
60
|
case 0:
|
|
@@ -55,31 +66,36 @@ exports.getFile = {
|
|
|
55
66
|
fileStorage = (0, FileStorage_1.getFileStorage)();
|
|
56
67
|
_a.label = 1;
|
|
57
68
|
case 1:
|
|
58
|
-
_a.trys.push([1,
|
|
69
|
+
_a.trys.push([1, 5, , 6]);
|
|
59
70
|
return [4 /*yield*/, fileStorage.getFile(fileId)];
|
|
60
71
|
case 2:
|
|
61
72
|
file = _a.sent();
|
|
62
|
-
return [4 /*yield*/, fileStorage.getFileData(fileId)];
|
|
63
|
-
case 3:
|
|
64
|
-
fileDataBase64 = _a.sent();
|
|
65
|
-
// The file row decides existence; empty data is a legitimate zero-byte file, not a 404.
|
|
66
73
|
if (!file) {
|
|
67
74
|
response.status(404).send('File not found');
|
|
75
|
+
return [2 /*return*/];
|
|
68
76
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
response.setHeader('
|
|
74
|
-
response.
|
|
77
|
+
return [4 /*yield*/, fileStorage.getSignedUrl(fileId)];
|
|
78
|
+
case 3:
|
|
79
|
+
signedUrl = _a.sent();
|
|
80
|
+
if (signedUrl) {
|
|
81
|
+
response.setHeader('Cache-Control', 'private, max-age=300');
|
|
82
|
+
response.redirect(302, signedUrl);
|
|
83
|
+
return [2 /*return*/];
|
|
75
84
|
}
|
|
76
|
-
return [
|
|
85
|
+
return [4 /*yield*/, fileStorage.getFileData(fileId)];
|
|
77
86
|
case 4:
|
|
87
|
+
fileDataBase64 = _a.sent();
|
|
88
|
+
safeFilename = encodeURIComponent(file.name);
|
|
89
|
+
response.setHeader('Content-Disposition', "inline; filename=\"".concat(safeFilename, "\""));
|
|
90
|
+
response.setHeader('Content-Type', file.type);
|
|
91
|
+
response.send(Buffer.from(fileDataBase64, 'base64'));
|
|
92
|
+
return [3 /*break*/, 6];
|
|
93
|
+
case 5:
|
|
78
94
|
error_1 = _a.sent();
|
|
79
95
|
console.error("Error fetching file (".concat(fileId, "):"), error_1);
|
|
80
96
|
response.status(500).send('Internal Server Error');
|
|
81
|
-
return [3 /*break*/,
|
|
82
|
-
case
|
|
97
|
+
return [3 /*break*/, 6];
|
|
98
|
+
case 6: return [2 /*return*/];
|
|
83
99
|
}
|
|
84
100
|
});
|
|
85
101
|
}); },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getFile.js","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8CAAgD;AAChD,wCAA2C;
|
|
1
|
+
{"version":3,"file":"getFile.js","sourceRoot":"","sources":["../../../src/routes/getFile.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8CAAgD;AAChD,wCAA2C;AAE3C;;;;;;;;;;GAUG;AACU,QAAA,OAAO,GAAU;IAC5B,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,KAAK;IACb,SAAS,EAAE,UAAO,OAAO,EAAE,QAAQ;;;;;oBACjC,IAAI,CAAC,eAAQ,CAAC,UAAU,EAAE,EAAE;wBAC1B,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;wBAChD,sBAAO;qBACR;oBAEK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3B,WAAW,GAAG,IAAA,4BAAc,GAAE,CAAC;;;;oBAGtB,qBAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAA;;oBAAxC,IAAI,GAAG,SAAiC;oBAC9C,IAAI,CAAC,IAAI,EAAE;wBACT,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBAC5C,sBAAO;qBACR;oBAEiB,qBAAM,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC,EAAA;;oBAAlD,SAAS,GAAG,SAAsC;oBACxD,IAAI,SAAS,EAAE;wBACb,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;wBAC5D,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;wBAClC,sBAAO;qBACR;oBAEsB,qBAAM,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,EAAA;;oBAAtD,cAAc,GAAG,SAAqC;oBACtD,YAAY,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACnD,QAAQ,CAAC,SAAS,CAAC,qBAAqB,EAAE,6BAAqB,YAAY,OAAG,CAAC,CAAC;oBAChF,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC9C,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;;;;oBAErD,OAAO,CAAC,KAAK,CAAC,+BAAwB,MAAM,OAAI,EAAE,OAAK,CAAC,CAAC;oBACzD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;;;;;SAEtD;CACF,CAAC"}
|
|
@@ -10,6 +10,15 @@ export interface File extends ScopedRecord {
|
|
|
10
10
|
* the bytes stay out of the DB row (consistent with the storage model); deleted with this file.
|
|
11
11
|
*/
|
|
12
12
|
preview?: Reference<File>;
|
|
13
|
+
/**
|
|
14
|
+
* Media metadata — generic file facts (an image/video's pixel dimensions, a video/audio
|
|
15
|
+
* duration) every consumer needs to render without loading bytes, e.g. reserving a media box's
|
|
16
|
+
* aspect ratio before any bytes arrive. Set at ingest for media files; absent for everything
|
|
17
|
+
* else.
|
|
18
|
+
*/
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
durationMs?: number;
|
|
13
22
|
}
|
|
14
23
|
export declare class FileTable extends Table<File> {
|
|
15
24
|
name: string;
|
|
@@ -24,10 +33,9 @@ export declare class FileTable extends Table<File> {
|
|
|
24
33
|
type: import("@proteinjs/db").Column<string, any>;
|
|
25
34
|
size: import("@proteinjs/db").Column<number, any>;
|
|
26
35
|
preview: import("@proteinjs/db").Column<Reference<File> | undefined, any>;
|
|
36
|
+
width: import("@proteinjs/db").Column<number | undefined, any>;
|
|
37
|
+
height: import("@proteinjs/db").Column<number | undefined, any>;
|
|
38
|
+
durationMs: import("@proteinjs/db").Column<number | undefined, any>;
|
|
27
39
|
} & {};
|
|
28
|
-
cascadeDeleteReferences: () => {
|
|
29
|
-
table: string;
|
|
30
|
-
referenceColumn: string;
|
|
31
|
-
}[];
|
|
32
40
|
}
|
|
33
41
|
//# sourceMappingURL=FileTable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileTable.d.ts","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgD,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EAAE,YAAY,EAA2B,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"FileTable.d.ts","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgD,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EAAE,YAAY,EAA2B,MAAM,iBAAiB,CAAC;AAIxE,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;CACrB;AAED,qBAAa,SAAU,SAAQ,KAAK,CAAC,IAAI,CAAC;IACjC,IAAI,SAAmB;IACvB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAO9B;IACK,OAAO;;;;;;;;;;;;;WASX;CAGJ"}
|
|
@@ -18,7 +18,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.FileTable = void 0;
|
|
19
19
|
var db_1 = require("@proteinjs/db");
|
|
20
20
|
var user_1 = require("@proteinjs/user");
|
|
21
|
-
var FileDataTable_1 = require("./FileDataTable");
|
|
22
21
|
var FILE_TABLE_NAME = 'file';
|
|
23
22
|
var FileTable = /** @class */ (function (_super) {
|
|
24
23
|
__extends(FileTable, _super);
|
|
@@ -39,14 +38,13 @@ var FileTable = /** @class */ (function (_super) {
|
|
|
39
38
|
size: new db_1.IntegerColumn('size'),
|
|
40
39
|
// Self-reference (the preview is itself a File). cascadeDelete: removing a file removes its preview.
|
|
41
40
|
preview: new db_1.ReferenceColumn('preview', FILE_TABLE_NAME, true),
|
|
41
|
+
width: new db_1.IntegerColumn('width'),
|
|
42
|
+
height: new db_1.IntegerColumn('height'),
|
|
43
|
+
durationMs: new db_1.IntegerColumn('duration_ms'),
|
|
42
44
|
});
|
|
43
|
-
_this.cascadeDeleteReferences = function () { return [
|
|
44
|
-
{
|
|
45
|
-
table: new FileDataTable_1.FileDataTable().name,
|
|
46
|
-
referenceColumn: new FileDataTable_1.FileDataTable().columns.file.name,
|
|
47
|
-
},
|
|
48
|
-
]; };
|
|
49
45
|
return _this;
|
|
46
|
+
// No cascadeDeleteReferences for FileData: byte cleanup (FileData rows included) is owned by
|
|
47
|
+
// FileStorageDriver.deleteFile, invoked for every file-row delete by FileStorageTableWatcher.
|
|
50
48
|
}
|
|
51
49
|
return FileTable;
|
|
52
50
|
}(db_1.Table));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileTable.js","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,oCAA+F;AAC/F,wCAAwE;
|
|
1
|
+
{"version":3,"file":"FileTable.js","sourceRoot":"","sources":["../../../src/tables/FileTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,oCAA+F;AAC/F,wCAAwE;AAExE,IAAM,eAAe,GAAG,MAAM,CAAC;AAuB/B;IAA+B,6BAAW;IAA1C;QAAA,qEAsBC;QArBQ,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;SAC7C,CAAC,CAAC;;QACH,6FAA6F;QAC7F,8FAA8F;IAChG,CAAC;IAAD,gBAAC;AAAD,CAAC,AAtBD,CAA+B,UAAK,GAsBnC;AAtBY,8BAAS"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileMediaMetadata.test.d.ts","sourceRoot":"","sources":["../../test/FileMediaMetadata.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,154 @@
|
|
|
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 FileTestEnvironment_1 = require("./FileTestEnvironment");
|
|
43
|
+
var TestMemoryFileStorageDriver = /** @class */ (function () {
|
|
44
|
+
function TestMemoryFileStorageDriver() {
|
|
45
|
+
this.store = new Map();
|
|
46
|
+
}
|
|
47
|
+
TestMemoryFileStorageDriver.prototype.createFile = function (file, fileData) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
49
|
+
return __generator(this, function (_a) {
|
|
50
|
+
this.store.set(file.id, fileData);
|
|
51
|
+
return [2 /*return*/];
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
TestMemoryFileStorageDriver.prototype.getFileData = function (fileId) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
57
|
+
var data;
|
|
58
|
+
return __generator(this, function (_a) {
|
|
59
|
+
data = this.store.get(fileId);
|
|
60
|
+
if (data === undefined) {
|
|
61
|
+
throw new Error("No such object: ".concat(fileId));
|
|
62
|
+
}
|
|
63
|
+
return [2 /*return*/, data];
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
TestMemoryFileStorageDriver.prototype.updateFileData = function (fileId, data) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
69
|
+
return __generator(this, function (_a) {
|
|
70
|
+
this.store.set(fileId, data);
|
|
71
|
+
return [2 /*return*/];
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
TestMemoryFileStorageDriver.prototype.deleteFile = function (fileId) {
|
|
76
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
77
|
+
return __generator(this, function (_a) {
|
|
78
|
+
this.store.delete(fileId);
|
|
79
|
+
return [2 /*return*/];
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
return TestMemoryFileStorageDriver;
|
|
84
|
+
}());
|
|
85
|
+
var testEnv = new FileTestEnvironment_1.FileTestEnvironment();
|
|
86
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
87
|
+
var user;
|
|
88
|
+
return __generator(this, function (_a) {
|
|
89
|
+
switch (_a.label) {
|
|
90
|
+
case 0: return [4 /*yield*/, testEnv.beforeAll()];
|
|
91
|
+
case 1:
|
|
92
|
+
_a.sent();
|
|
93
|
+
return [4 /*yield*/, testEnv.createUser({ name: 'File owner', email: 'media-metadata@test.local' })];
|
|
94
|
+
case 2:
|
|
95
|
+
user = _a.sent();
|
|
96
|
+
testEnv.actAs(user);
|
|
97
|
+
testEnv.setDriver(new TestMemoryFileStorageDriver());
|
|
98
|
+
return [2 /*return*/];
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}); });
|
|
102
|
+
afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
103
|
+
return __generator(this, function (_a) {
|
|
104
|
+
switch (_a.label) {
|
|
105
|
+
case 0: return [4 /*yield*/, testEnv.afterAll()];
|
|
106
|
+
case 1:
|
|
107
|
+
_a.sent();
|
|
108
|
+
return [2 /*return*/];
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}); });
|
|
112
|
+
/**
|
|
113
|
+
* Media metadata lives on the File row (`width`/`height`/`durationMs`) — generic file facts every
|
|
114
|
+
* consumer needs to render without loading bytes (aspect-ratio-reserved layout). This proves the
|
|
115
|
+
* columns persist and round-trip through the real schema on the emulator.
|
|
116
|
+
*/
|
|
117
|
+
describe('File media metadata columns', function () {
|
|
118
|
+
it('round-trips width/height/durationMs on a media file', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
119
|
+
var file, row;
|
|
120
|
+
return __generator(this, function (_a) {
|
|
121
|
+
switch (_a.label) {
|
|
122
|
+
case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: 'clip.mp4', type: 'video/mp4', size: 4, width: 1280, height: 720, durationMs: 24500 }, Buffer.from('clip').toString('base64'))];
|
|
123
|
+
case 1:
|
|
124
|
+
file = _a.sent();
|
|
125
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(tables_1.tables.File, { id: file.id })];
|
|
126
|
+
case 2:
|
|
127
|
+
row = _a.sent();
|
|
128
|
+
expect(row.width).toEqual(1280);
|
|
129
|
+
expect(row.height).toEqual(720);
|
|
130
|
+
expect(row.durationMs).toEqual(24500);
|
|
131
|
+
return [2 /*return*/];
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}); });
|
|
135
|
+
it('leaves the fields absent for non-media files', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
136
|
+
var file, row;
|
|
137
|
+
var _a, _b, _c;
|
|
138
|
+
return __generator(this, function (_d) {
|
|
139
|
+
switch (_d.label) {
|
|
140
|
+
case 0: return [4 /*yield*/, new FileStorage_1.FileStorage().createFile({ name: 'a.txt', type: 'text/plain', size: 5 }, Buffer.from('hello').toString('base64'))];
|
|
141
|
+
case 1:
|
|
142
|
+
file = _d.sent();
|
|
143
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(tables_1.tables.File, { id: file.id })];
|
|
144
|
+
case 2:
|
|
145
|
+
row = _d.sent();
|
|
146
|
+
expect((_a = row.width) !== null && _a !== void 0 ? _a : undefined).toBeUndefined();
|
|
147
|
+
expect((_b = row.height) !== null && _b !== void 0 ? _b : undefined).toBeUndefined();
|
|
148
|
+
expect((_c = row.durationMs) !== null && _c !== void 0 ? _c : undefined).toBeUndefined();
|
|
149
|
+
return [2 /*return*/];
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}); });
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=FileMediaMetadata.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileMediaMetadata.test.js","sourceRoot":"","sources":["../../test/FileMediaMetadata.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileStorageDeleteFile.test.d.ts","sourceRoot":"","sources":["../../test/FileStorageDeleteFile.test.ts"],"names":[],"mappings":""}
|