@proteinjs/db-file 1.7.3 → 1.7.5

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 (39) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/generated/index.js +1 -1
  3. package/dist/generated/index.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/src/FileReachabilityResolver.d.ts +27 -0
  9. package/dist/src/FileReachabilityResolver.d.ts.map +1 -0
  10. package/dist/src/FileReachabilityResolver.js +9 -0
  11. package/dist/src/FileReachabilityResolver.js.map +1 -0
  12. package/dist/src/FileStorage.d.ts +28 -3
  13. package/dist/src/FileStorage.d.ts.map +1 -1
  14. package/dist/src/FileStorage.js +72 -11
  15. package/dist/src/FileStorage.js.map +1 -1
  16. package/dist/src/routes/getFile.d.ts +4 -1
  17. package/dist/src/routes/getFile.d.ts.map +1 -1
  18. package/dist/src/routes/getFile.js +4 -1
  19. package/dist/src/routes/getFile.js.map +1 -1
  20. package/dist/test/FileByteAccess.test.d.ts +2 -0
  21. package/dist/test/FileByteAccess.test.d.ts.map +1 -0
  22. package/dist/test/FileByteAccess.test.js +252 -0
  23. package/dist/test/FileByteAccess.test.js.map +1 -0
  24. package/dist/test/FileSharedReachability.test.d.ts +2 -0
  25. package/dist/test/FileSharedReachability.test.d.ts.map +1 -0
  26. package/dist/test/FileSharedReachability.test.js +358 -0
  27. package/dist/test/FileSharedReachability.test.js.map +1 -0
  28. package/dist/test/FileTestEnvironment.d.ts.map +1 -1
  29. package/dist/test/FileTestEnvironment.js +4 -0
  30. package/dist/test/FileTestEnvironment.js.map +1 -1
  31. package/generated/index.ts +1 -1
  32. package/index.ts +1 -0
  33. package/package.json +2 -2
  34. package/src/FileReachabilityResolver.ts +29 -0
  35. package/src/FileStorage.ts +53 -4
  36. package/src/routes/getFile.ts +5 -2
  37. package/test/FileByteAccess.test.ts +143 -0
  38. package/test/FileSharedReachability.test.ts +229 -0
  39. package/test/FileTestEnvironment.ts +4 -0
@@ -111,6 +111,10 @@ var FileTestEnvironment = /** @class */ (function () {
111
111
  objectCache['@proteinjs/db/Table'] = __spreadArray(__spreadArray([], Object.values(user_1.tables), true), Object.values(tables_1.tables), true);
112
112
  // The byte-deletion seam under test: prod loads it from the generated source graph.
113
113
  objectCache['@proteinjs/db/TableWatcher'] = [new FileStorageTableWatcher_1.FileStorageTableWatcher()];
114
+ // The shared-content reachability seam getFile consults on a scoped-read miss (prod loads
115
+ // implementations from the generated source graph; a graph-less harness lookup THROWS).
116
+ // Default: no registered resolvers. Suites exercising the seam overwrite this entry.
117
+ objectCache['@proteinjs/db-file/FileReachabilityResolver'] = [];
114
118
  server_api_1.Session.setData({
115
119
  sessionId: 'test-session',
116
120
  user: 'guest',
@@ -1 +1 @@
1
- {"version":3,"file":"FileTestEnvironment.js","sourceRoot":"","sources":["../../test/FileTestEnvironment.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FileTestEnvironment.js","sourceRoot":"","sources":["../../test/FileTestEnvironment.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oCAAyD;AACzD,kEAA6D;AAC7D,0DAAiG;AACjG,oDAA2F;AAC3F,oDAAyD;AACzD,wCAAuE;AACvE,+CAA4D;AAC5D,kDAAiD;AAEjD,0EAAyE;AAEzE;oFACoF;AACpF;IAAA;QACE,gBAAW,GAAG,MAAe,CAAC;IAUhC,CAAC;IAPC,wCAAO,GAAP,UAAQ,IAAiB;QACvB,sBAAsB,CAAC,IAAI,GAAG,IAAI,CAAC;IACrC,CAAC;IAED,wCAAO,GAAP;QACE,OAAO,sBAAsB,CAAC,IAAI,CAAC;IACrC,CAAC;IACH,6BAAC;AAAD,CAAC,AAXD,IAWC;AAKD;;;;;GAKG;AACH;IAAA;QACW,kBAAa,GAAG,IAAI,iCAAa,CAAC;YACzC,SAAS,EAAE,gBAAgB;YAC3B,YAAY,EAAE,gBAAgB;YAC9B,YAAY,EAAE,MAAM;SACrB,CAAC,CAAC;QACK,kBAAa,GAAG,IAAA,uBAAgB,EAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrD,aAAQ,GAAG,IAAI,eAAQ,EAAE,CAAC;IA+EpC,CAAC;IA7EO,uCAAS,GAAf;;;;;;;oBACE,sFAAsF;oBACtF,wFAAwF;oBACxF,qBAAM,iCAA0B,CAAC,iBAAiB,CAAC;4BACjD,SAAS,EAAE,gBAAgB;4BAC3B,YAAY,EAAE,gBAAgB;4BAC9B,YAAY,EAAE,MAAM;yBACrB,CAAC,EAAA;;wBANF,sFAAsF;wBACtF,wFAAwF;wBACxF,SAIE,CAAC;wBACG,WAAW,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;wBACvC,WAAW,CAAC,0CAA0C,CAAC,GAAG,CAAC,IAAI,sBAAsB,EAAE,CAAC,CAAC;wBACzF,WAAW,CAAC,sCAAsC,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,cAAM,OAAA,KAAI,CAAC,aAAa,EAAlB,CAAkB,EAAE,CAAC,CAAC;wBAClG,oFAAoF;wBACpF,gFAAgF;wBAChF,WAAW,CAAC,qBAAqB,CAAC,GAAG,gCAAI,MAAM,CAAC,MAAM,CAAC,aAAU,CAAC,SAAK,MAAM,CAAC,MAAM,CAAC,eAAU,CAAC,OAAc,CAAC;wBAC/G,oFAAoF;wBACpF,WAAW,CAAC,4BAA4B,CAAC,GAAG,CAAC,IAAI,iDAAuB,EAAE,CAAC,CAAC;wBAC5E,0FAA0F;wBAC1F,wFAAwF;wBACxF,qFAAqF;wBACrF,WAAW,CAAC,6CAA6C,CAAC,GAAG,EAAE,CAAC;wBAChE,oBAAO,CAAC,OAAO,CAAC;4BACd,SAAS,EAAE,cAAc;4BACzB,IAAI,EAAE,OAAO;4BACb,IAAI,EAAE,EAAE;yBACT,CAAC,CAAC;wBAEH,qBAAM,IAAI,CAAC,UAAU,CAAC,aAAU,CAAC,EAAA;;wBAAjC,SAAiC,CAAC;wBAClC,qBAAM,IAAI,CAAC,UAAU,CAAC,eAAU,CAAC,EAAA;;wBAAjC,SAAiC,CAAC;;;;;KACnC;IAEK,sCAAQ,GAAd;;;;4BACE,qBAAM,IAAI,CAAC,UAAU,CAAC,eAAU,CAAC,EAAA;;wBAAjC,SAAiC,CAAC;wBAClC,qBAAM,IAAI,CAAC,UAAU,CAAC,aAAU,CAAC,EAAA;;wBAAjC,SAAiC,CAAC;wBAClC,iCAA0B,CAAC,OAAO,EAAE,CAAC;;;;;KACtC;IAED;;;OAGG;IACH,uCAAS,GAAT,UAAU,MAAyB;QACjC,IAAI,CAAC,WAAW,EAAE,CAAC,oDAAoD,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,cAAM,OAAA,MAAM,EAAN,CAAM,EAAE,CAAC,CAAC;QACxG,yBAA+C,CAAC,MAAM,GAAG,SAAS,CAAC;IACtE,CAAC;IAED,mDAAmD;IAC7C,wCAAU,GAAhB,UAAiB,IAAuD;;;;;4BAC/D,qBAAM,IAAA,kBAAa,GAAE,CAAC,MAAM,CAAC,aAAU,CAAC,IAAI,EAAE;4BACnD,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,KAAK,EAAE,IAAI,CAAC,KAAK;4BACjB,QAAQ,EAAE,MAAM;4BAChB,aAAa,EAAE,IAAI;4BACnB,KAAK,EAAE,MAAA,IAAI,CAAC,KAAK,mCAAI,EAAE;yBACxB,CAAC,EAAA;4BANF,sBAAO,SAML,EAAC;;;;KACJ;IAED,mGAAmG;IACnG,mCAAK,GAAL,UAAM,IAAU;QACd,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAEO,yCAAW,GAAnB;QACE,OAAQ,6BAAgB,CAAC,GAAG,EAA2C,CAAC,WAAW,CAAC;IACtF,CAAC;IAEa,wCAAU,GAAxB,UAAyB,MAAqC;;;;;;wBACtD,QAAQ,GAAG,OAAE,CAAC,kBAAkB,EAAE,CAAC;8BACA,EAArB,KAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;;;6BAArB,CAAA,cAAqB,CAAA;wBAA9B,KAAK;wBACd,qBAAM,QAAQ,CAAC,eAAe,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAA;;wBAAjD,SAAiD,CAAC;;;wBADhC,IAAqB,CAAA;;;;;;KAG1C;IAEa,wCAAU,GAAxB,UAAyB,MAAqC;;;;;;8BACnB,EAArB,KAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;;;6BAArB,CAAA,cAAqB,CAAA;wBAA9B,KAAK;wBACd,qBAAM,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAA;;wBAA/B,SAA+B,CAAC;;;wBADd,IAAqB,CAAA;;;;;;KAG1C;IACH,0BAAC;AAAD,CAAC,AAtFD,IAsFC;AAtFY,kDAAmB"}
@@ -9,7 +9,7 @@ import '@proteinjs/user';
9
9
  /** Generate Source Graph */
10
10
 
11
11
  const sourceGraph =
12
- '{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-file/DefaultFileStorageDriverFactory","value":{"packageName":"@proteinjs/db-file","name":"DefaultFileStorageDriverFactory","filePath":"src/FileStorage.ts","qualifiedName":"@proteinjs/db-file/DefaultFileStorageDriverFactory","properties":[],"methods":[{"name":"getDriver","returnType":{"packageName":"@proteinjs/db-file","name":"FileStorageDriver","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageDriver","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[]}],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/reflection","name":"Loadable","filePath":null,"qualifiedName":"@proteinjs/reflection/Loadable","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/reflection/Loadable"},{"v":"@proteinjs/db-file/FileStorage","value":{"packageName":"@proteinjs/db-file","name":"FileStorage","filePath":"src/FileStorage.ts","qualifiedName":"@proteinjs/db-file/FileStorage","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"driver","type":{"packageName":"@proteinjs/db-file","name":"FileStorageDriver","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageDriver","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"serviceMetadata","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getDriver","returnType":{"packageName":"@proteinjs/db-file","name":"FileStorageDriver","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageDriver","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"public","parameters":[]},{"name":"createFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileMetaData","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}},{"name":"fileData","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFileData","returnType":{"packageName":"","name":"Promise<string>","filePath":null,"qualifiedName":"/Promise<string>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getSignedUrl","returnType":{"packageName":"","name":"Promise<string | undefined>","filePath":null,"qualifiedName":"/Promise<string | undefined>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"options","type":{"packageName":"@proteinjs/db-file","name":"{ ttlMs?: number }","filePath":null,"qualifiedName":"@proteinjs/db-file/{ ttlMs?: number }","typeParameters":null,"directParents":null}}]},{"name":"updateFileData","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"data","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"updateFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"file","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}}]},{"name":"deleteFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db-file","name":"FileStorageService","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageService","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db-file/FileStorageService","value":{"packageName":"@proteinjs/db-file","name":"FileStorageService","filePath":"src/services/FileStorageService.ts","qualifiedName":"@proteinjs/db-file/FileStorageService","properties":[],"methods":[{"name":"createFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileMetaData","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}},{"name":"fileData","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFileData","returnType":{"packageName":"","name":"Promise<string>","filePath":null,"qualifiedName":"/Promise<string>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"updateFileData","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"data","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"updateFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"file","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}}]},{"name":"deleteFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/service","name":"Service","filePath":null,"qualifiedName":"@proteinjs/service/Service","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db-file/FileStorageTableWatcher","value":{"packageName":"@proteinjs/db-file","name":"FileStorageTableWatcher","filePath":"src/FileStorageTableWatcher.ts","qualifiedName":"@proteinjs/db-file/FileStorageTableWatcher","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"name","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"table","returnType":{"packageName":"","name":"Table<File>","filePath":null,"qualifiedName":"/Table<File>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"beforeDelete","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"recordsToDelete","type":{"packageName":"","name":"T[]","filePath":null,"qualifiedName":"/T[]","typeParameters":null,"directParents":null}},{"name":"initialQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null}},{"name":"deleteQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"TableWatcher","filePath":null,"qualifiedName":"@proteinjs/db/TableWatcher","properties":[],"methods":[],"typeParameters":["@proteinjs/db-file/File"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/TableWatcher"},{"v":"@proteinjs/db-file/getFile","value":{"packageName":"@proteinjs/db-file","name":"getFile","filePath":"src/routes/getFile.ts","qualifiedName":"@proteinjs/db-file/getFile","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/server-api/Route"},{"v":"@proteinjs/service/Service"},{"v":"@proteinjs/db-file/FileData","value":{"packageName":"@proteinjs/db-file","name":"FileData","filePath":"src/tables/FileDataTable.ts","qualifiedName":"@proteinjs/db-file/FileData","properties":[{"name":"file","type":{"packageName":"","name":"Reference<File>","filePath":null,"qualifiedName":"/Reference<File>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"order","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"data","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/user","name":"ScopedRecord","filePath":null,"qualifiedName":"@proteinjs/user/ScopedRecord","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/user/ScopedRecord"},{"v":"@proteinjs/db-file/FileDataTable","value":{"packageName":"@proteinjs/db-file","name":"FileDataTable","filePath":"src/tables/FileDataTable.ts","qualifiedName":"@proteinjs/db-file/FileDataTable","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"name","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"auth","type":{"packageName":"","name":"Table<FileData>[\'auth\']","filePath":null,"qualifiedName":"/Table<FileData>[\'auth\']","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"columns","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"Table","filePath":null,"qualifiedName":"@proteinjs/db/Table","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":["@proteinjs/db-file/FileData"],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/Table"},{"v":"@proteinjs/db-file/File","value":{"packageName":"@proteinjs/db-file","name":"File","filePath":"src/tables/FileTable.ts","qualifiedName":"@proteinjs/db-file/File","properties":[{"name":"name","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"type","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"size","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"preview","type":{"packageName":"","name":"Reference<File>","filePath":null,"qualifiedName":"/Reference<File>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"width","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"height","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"durationMs","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"sourceUrl","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"sourcePageUrl","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"retrievedAt","type":{"packageName":"","name":"Date","filePath":null,"qualifiedName":"/Date","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"contentHash","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/user","name":"ScopedRecord","filePath":null,"qualifiedName":"@proteinjs/user/ScopedRecord","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db-file/FileTable","value":{"packageName":"@proteinjs/db-file","name":"FileTable","filePath":"src/tables/FileTable.ts","qualifiedName":"@proteinjs/db-file/FileTable","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"name","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"auth","type":{"packageName":"","name":"Table<File>[\'auth\']","filePath":null,"qualifiedName":"/Table<File>[\'auth\']","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"columns","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"indexes","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"Table","filePath":null,"qualifiedName":"@proteinjs/db/Table","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":["@proteinjs/db-file/File"],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}}],"edges":[{"v":"@proteinjs/db-file/DefaultFileStorageDriverFactory","w":"@proteinjs/reflection/Loadable","value":"extends interface"},{"v":"@proteinjs/db-file/FileStorage","w":"@proteinjs/db-file/FileStorageService","value":"implements interface"},{"v":"@proteinjs/db-file/FileStorageTableWatcher","w":"@proteinjs/db/TableWatcher","value":"implements interface"},{"v":"@proteinjs/db-file/getFile","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/db-file/FileStorageService","w":"@proteinjs/service/Service","value":"extends interface"},{"v":"@proteinjs/db-file/FileData","w":"@proteinjs/user/ScopedRecord","value":"extends interface"},{"v":"@proteinjs/db-file/FileDataTable","w":"@proteinjs/db/Table","value":"extends class"},{"v":"@proteinjs/db-file/File","w":"@proteinjs/user/ScopedRecord","value":"extends interface"},{"v":"@proteinjs/db-file/FileTable","w":"@proteinjs/db/Table","value":"extends class"}]}';
12
+ '{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-file/FileReachabilityResolver","value":{"packageName":"@proteinjs/db-file","name":"FileReachabilityResolver","filePath":"src/FileReachabilityResolver.ts","qualifiedName":"@proteinjs/db-file/FileReachabilityResolver","properties":[],"methods":[{"name":"canReadViaReference","returnType":{"packageName":"","name":"Promise<boolean>","filePath":null,"qualifiedName":"/Promise<boolean>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/reflection","name":"Loadable","filePath":null,"qualifiedName":"@proteinjs/reflection/Loadable","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/reflection/Loadable"},{"v":"@proteinjs/db-file/DefaultFileStorageDriverFactory","value":{"packageName":"@proteinjs/db-file","name":"DefaultFileStorageDriverFactory","filePath":"src/FileStorage.ts","qualifiedName":"@proteinjs/db-file/DefaultFileStorageDriverFactory","properties":[],"methods":[{"name":"getDriver","returnType":{"packageName":"@proteinjs/db-file","name":"FileStorageDriver","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageDriver","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[]}],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/reflection","name":"Loadable","filePath":null,"qualifiedName":"@proteinjs/reflection/Loadable","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db-file/FileStorage","value":{"packageName":"@proteinjs/db-file","name":"FileStorage","filePath":"src/FileStorage.ts","qualifiedName":"@proteinjs/db-file/FileStorage","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"driver","type":{"packageName":"@proteinjs/db-file","name":"FileStorageDriver","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageDriver","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"serviceMetadata","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getDriver","returnType":{"packageName":"@proteinjs/db-file","name":"FileStorageDriver","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageDriver","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"public","parameters":[]},{"name":"createFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileMetaData","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}},{"name":"fileData","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFileData","returnType":{"packageName":"","name":"Promise<string>","filePath":null,"qualifiedName":"/Promise<string>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getSignedUrl","returnType":{"packageName":"","name":"Promise<string | undefined>","filePath":null,"qualifiedName":"/Promise<string | undefined>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"options","type":{"packageName":"@proteinjs/db-file","name":"{ ttlMs?: number }","filePath":null,"qualifiedName":"@proteinjs/db-file/{ ttlMs?: number }","typeParameters":null,"directParents":null}}]},{"name":"updateFileData","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"data","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"updateFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"file","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}}]},{"name":"deleteFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db-file","name":"FileStorageService","filePath":null,"qualifiedName":"@proteinjs/db-file/FileStorageService","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db-file/FileStorageService","value":{"packageName":"@proteinjs/db-file","name":"FileStorageService","filePath":"src/services/FileStorageService.ts","qualifiedName":"@proteinjs/db-file/FileStorageService","properties":[],"methods":[{"name":"createFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileMetaData","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}},{"name":"fileData","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFile","returnType":{"packageName":"","name":"Promise<File>","filePath":null,"qualifiedName":"/Promise<File>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getFileData","returnType":{"packageName":"","name":"Promise<string>","filePath":null,"qualifiedName":"/Promise<string>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"updateFileData","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"data","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"updateFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"file","type":{"packageName":"","name":"Omit<File, keyof ScopedRecord>","filePath":null,"qualifiedName":"/Omit<File, keyof ScopedRecord>","typeParameters":null,"directParents":null}}]},{"name":"deleteFile","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":true,"isStatic":false,"visibility":"public","parameters":[{"name":"fileId","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/service","name":"Service","filePath":null,"qualifiedName":"@proteinjs/service/Service","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db-file/FileStorageTableWatcher","value":{"packageName":"@proteinjs/db-file","name":"FileStorageTableWatcher","filePath":"src/FileStorageTableWatcher.ts","qualifiedName":"@proteinjs/db-file/FileStorageTableWatcher","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"name","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"table","returnType":{"packageName":"","name":"Table<File>","filePath":null,"qualifiedName":"/Table<File>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"beforeDelete","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"recordsToDelete","type":{"packageName":"","name":"T[]","filePath":null,"qualifiedName":"/T[]","typeParameters":null,"directParents":null}},{"name":"initialQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null}},{"name":"deleteQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"TableWatcher","filePath":null,"qualifiedName":"@proteinjs/db/TableWatcher","properties":[],"methods":[],"typeParameters":["@proteinjs/db-file/File"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/TableWatcher"},{"v":"@proteinjs/db-file/getFile","value":{"packageName":"@proteinjs/db-file","name":"getFile","filePath":"src/routes/getFile.ts","qualifiedName":"@proteinjs/db-file/getFile","type":{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":[{"packageName":"@proteinjs/server-api","name":"Route","filePath":null,"qualifiedName":"@proteinjs/server-api/Route","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/server-api/Route"},{"v":"@proteinjs/db-file/FileData","value":{"packageName":"@proteinjs/db-file","name":"FileData","filePath":"src/tables/FileDataTable.ts","qualifiedName":"@proteinjs/db-file/FileData","properties":[{"name":"file","type":{"packageName":"","name":"Reference<File>","filePath":null,"qualifiedName":"/Reference<File>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"order","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"data","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/user","name":"ScopedRecord","filePath":null,"qualifiedName":"@proteinjs/user/ScopedRecord","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/user/ScopedRecord"},{"v":"@proteinjs/db-file/FileDataTable","value":{"packageName":"@proteinjs/db-file","name":"FileDataTable","filePath":"src/tables/FileDataTable.ts","qualifiedName":"@proteinjs/db-file/FileDataTable","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"name","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"auth","type":{"packageName":"","name":"Table<FileData>[\'auth\']","filePath":null,"qualifiedName":"/Table<FileData>[\'auth\']","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"columns","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"Table","filePath":null,"qualifiedName":"@proteinjs/db/Table","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":["@proteinjs/db-file/FileData"],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/Table"},{"v":"@proteinjs/db-file/File","value":{"packageName":"@proteinjs/db-file","name":"File","filePath":"src/tables/FileTable.ts","qualifiedName":"@proteinjs/db-file/File","properties":[{"name":"name","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"type","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"size","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"preview","type":{"packageName":"","name":"Reference<File>","filePath":null,"qualifiedName":"/Reference<File>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"width","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"height","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"durationMs","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"sourceUrl","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"sourcePageUrl","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"retrievedAt","type":{"packageName":"","name":"Date","filePath":null,"qualifiedName":"/Date","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"contentHash","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/user","name":"ScopedRecord","filePath":null,"qualifiedName":"@proteinjs/user/ScopedRecord","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db-file/FileTable","value":{"packageName":"@proteinjs/db-file","name":"FileTable","filePath":"src/tables/FileTable.ts","qualifiedName":"@proteinjs/db-file/FileTable","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"name","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"auth","type":{"packageName":"","name":"Table<File>[\'auth\']","filePath":null,"qualifiedName":"/Table<File>[\'auth\']","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"columns","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"indexes","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"Table","filePath":null,"qualifiedName":"@proteinjs/db/Table","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":["@proteinjs/db-file/File"],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/service/Service"}],"edges":[{"v":"@proteinjs/db-file/FileReachabilityResolver","w":"@proteinjs/reflection/Loadable","value":"extends interface"},{"v":"@proteinjs/db-file/DefaultFileStorageDriverFactory","w":"@proteinjs/reflection/Loadable","value":"extends interface"},{"v":"@proteinjs/db-file/FileStorage","w":"@proteinjs/db-file/FileStorageService","value":"implements interface"},{"v":"@proteinjs/db-file/FileStorageTableWatcher","w":"@proteinjs/db/TableWatcher","value":"implements interface"},{"v":"@proteinjs/db-file/getFile","w":"@proteinjs/server-api/Route","value":"has type"},{"v":"@proteinjs/db-file/FileData","w":"@proteinjs/user/ScopedRecord","value":"extends interface"},{"v":"@proteinjs/db-file/FileDataTable","w":"@proteinjs/db/Table","value":"extends class"},{"v":"@proteinjs/db-file/File","w":"@proteinjs/user/ScopedRecord","value":"extends interface"},{"v":"@proteinjs/db-file/FileTable","w":"@proteinjs/db/Table","value":"extends class"},{"v":"@proteinjs/db-file/FileStorageService","w":"@proteinjs/service/Service","value":"extends interface"}]}';
13
13
 
14
14
  /** Generate Source Links */
15
15
 
package/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from './src/services/FileStorageService';
10
10
 
11
11
  export * from './src/routes/getFileRoute';
12
12
 
13
+ export * from './src/FileReachabilityResolver';
13
14
  export * from './src/FileStorageDriver';
14
15
  export * from './src/DbFileStorageDriver';
15
16
  export * from './src/FileStorageTableWatcher';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db-file",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "description": "File storage in the db",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -46,5 +46,5 @@
46
46
  },
47
47
  "main": "./dist/generated/index.js",
48
48
  "types": "./dist/generated/index.d.ts",
49
- "gitHead": "b25c88ae26fffd1c784b4c7b1174825b52751987"
49
+ "gitHead": "31b2584ebfcf690d1472d2ce9f1f75dc79a97481"
50
50
  }
@@ -0,0 +1,29 @@
1
+ import { Loadable, SourceRepository } from '@proteinjs/reflection';
2
+
3
+ /**
4
+ * The shared-content file leg (SHARING_EXPANSION §1): the caller may read a file when they may
5
+ * read a row that references it. File rows are ScopedRecord — owner-private — but files get
6
+ * EMBEDDED in shareable content (a shared thought's media nodes), where the content's own access
7
+ * model says a share recipient may read them; a purely scoped row read as the access check
8
+ * refuses every recipient (the shared-thought media defect, SHARING_EXPANSION §0).
9
+ *
10
+ * Content packages register resolvers that answer reachability through their own GRANT-FILTERED
11
+ * reads — the same derivation the content itself is read through — so file access derives from
12
+ * content access, revocation included, and file scope is never widened. Dependency direction is
13
+ * the SharedThoughtResolver pattern: the interface lives here in db-file; implementations live
14
+ * in the content packages above (thought, later chat) and are discovered via SourceRepository.
15
+ *
16
+ * Consulted by `FileStorage.getFile` only after the caller's scoped read MISSES (the owner never
17
+ * pays for it), and it widens READS only: writes and deletes stay owner-scoped.
18
+ */
19
+ export interface FileReachabilityResolver extends Loadable {
20
+ /**
21
+ * True when the CURRENT session user can read a row that references this file. Implementations
22
+ * MUST resolve through their grant-filtered reads (e.g. the shared db) so a revoked grant
23
+ * closes file access the same moment it closes content access.
24
+ */
25
+ canReadViaReference(fileId: string): Promise<boolean>;
26
+ }
27
+
28
+ export const getFileReachabilityResolvers = () =>
29
+ SourceRepository.get().objects<FileReachabilityResolver>('@proteinjs/db-file/FileReachabilityResolver');
@@ -1,8 +1,9 @@
1
- import { ScopedRecord, getScopedDb } from '@proteinjs/user';
1
+ import { ScopedRecord, getScopedDb, getScopedDbAsSystem } from '@proteinjs/user';
2
2
  import { File } from './tables/FileTable';
3
3
  import { tables } from './tables/tables';
4
4
  import { FileStorageService, getFileStorageService } from './services/FileStorageService';
5
5
  import { FileStorageDriver } from './FileStorageDriver';
6
+ import { getFileReachabilityResolvers } from './FileReachabilityResolver';
6
7
  import { Loadable, SourceRepository } from '@proteinjs/reflection';
7
8
  import { Logger } from '@proteinjs/logger';
8
9
  import { DbFileStorageDriver } from './DbFileStorageDriver';
@@ -73,29 +74,64 @@ export class FileStorage implements FileStorageService {
73
74
  }
74
75
 
75
76
  /**
76
- * Retrieves the metadata of a given file.
77
+ * Retrieves the metadata of a given file — THE file-read access decision every serving path
78
+ * derives from (`/file/:id`, signed-URL minting, the browser service).
79
+ *
80
+ * Two legs: the caller's own file resolves by the SCOPED read; when that misses, the
81
+ * shared-content leg asks the registered {@link FileReachabilityResolver}s whether the caller
82
+ * can read a row that REFERENCES the file (a shared thought's media node) — content access
83
+ * confers file access, through the content's own grant-filtered read, never by widening file
84
+ * scope. No resolver vouching means the miss stands. Reads only: writes/deletes stay scoped.
77
85
  * @param fileId - The `id` of the file.
78
- * @returns The file metadata.
86
+ * @returns The file metadata, or `undefined` when the caller can neither own nor reach it.
79
87
  */
80
88
  async getFile(fileId: string): Promise<File> {
81
89
  const db = getScopedDb();
82
90
  const file = await db.get(tables.File, { id: fileId });
91
+ if (file) {
92
+ return file;
93
+ }
94
+
95
+ for (const resolver of getFileReachabilityResolvers()) {
96
+ if (await resolver.canReadViaReference(fileId)) {
97
+ // Reachability established through the caller's grant-filtered content read — the row
98
+ // itself lives in the owner's scope, so it is served via system read.
99
+ return await getScopedDbAsSystem().get(tables.File, { id: fileId });
100
+ }
101
+ }
102
+
83
103
  return file;
84
104
  }
85
105
 
86
106
  /**
87
107
  * Retrieves the data chunks associated with a given file.
108
+ *
109
+ * Gated on the {@link getFile} row read — the same two-legged access decision every serving
110
+ * path derives from (the caller's SCOPED read, else shared-content reachability), so bytes are
111
+ * exactly as reachable as the row that names them. The gate lives here because this is the
112
+ * browser-facing service boundary (`FileStorageService` is `allUsers`) and the driver byte ops
113
+ * below are deliberately unscoped. Server-side doors that make their OWN documented access
114
+ * decision (the avatar route, issue/ticket attachment doors) read bytes through
115
+ * `FileStorage.getDriver()` instead.
88
116
  * @param fileId - The `id` of the file.
89
117
  * @returns The file data as a single string.
118
+ * @throws When the file row does not exist or is not readable by the caller — the same named
119
+ * miss either way, so existence is not leaked to unauthorized callers.
90
120
  */
91
121
  async getFileData(fileId: string): Promise<string> {
122
+ const file = await this.getFile(fileId);
123
+ if (!file) {
124
+ throw new Error(`File not found: ${fileId}`);
125
+ }
126
+
92
127
  return await FileStorage.getDriver().getFileData(fileId);
93
128
  }
94
129
 
95
130
  /**
96
131
  * Mint a short-lived, read-only URL for the file's bytes (server-only; not part of the
97
132
  * browser-facing `FileStorageService`). A signed URL is a bearer capability, so minting
98
- * verifies the caller can read the file row (scoped read) before signing.
133
+ * verifies the caller can read the file row (the two-legged {@link getFile} read own scope
134
+ * or shared-content reachability) before signing.
99
135
  * @param fileId - The `id` of the file.
100
136
  * @param options.ttlMs - How long the URL stays valid; the driver applies its default when omitted.
101
137
  * @returns The signed URL, or `undefined` when the driver has no external URL space
@@ -118,10 +154,23 @@ export class FileStorage implements FileStorageService {
118
154
 
119
155
  /**
120
156
  * Updates the data chunks associated with a given file.
157
+ *
158
+ * Gated on the caller's SCOPED row read — owner scope is the write grant on ScopedRecords
159
+ * (the same authorization a scoped row update enforces). Deliberately STRICTER than
160
+ * {@link getFileData}: the {@link FileReachabilityResolver} leg widens READS only, so a
161
+ * share recipient who can read the bytes still cannot write them.
121
162
  * @param fileId - The `id` of the file.
122
163
  * @param data - The new data string to replace the existing data.
164
+ * @throws When the file row does not exist or is not writable by the caller — the same named
165
+ * miss either way, so existence is not leaked to unauthorized callers.
123
166
  */
124
167
  async updateFileData(fileId: string, data: string): Promise<void> {
168
+ const db = getScopedDb();
169
+ const file = await db.get(tables.File, { id: fileId });
170
+ if (!file) {
171
+ throw new Error(`File not found: ${fileId}`);
172
+ }
173
+
125
174
  await FileStorage.getDriver().updateFileData(fileId, data);
126
175
  }
127
176
 
@@ -4,7 +4,10 @@ import { UserAuth } from '@proteinjs/user';
4
4
  import { resolveByteRange } from './byteRange';
5
5
 
6
6
  /**
7
- * Serves a file's bytes. Auth first (logged-in + scoped row read), then one of two paths:
7
+ * Serves a file's bytes. Auth first: logged-in, then the `FileStorage.getFile` row read as the
8
+ * access decision — the caller's SCOPED read, else the shared-content reachability leg
9
+ * (`FileReachabilityResolver`: the caller may read a file when they may read a row that
10
+ * references it, e.g. a shared thought's media node). Then one of two serving paths:
8
11
  *
9
12
  * - **302 redirect** to a short-lived signed URL when the driver's store has an external URL
10
13
  * space (GCS). The app URL stays the one stable reference every `<img>`/`<video>`/chip uses;
@@ -28,7 +31,7 @@ export const getFile: Route = {
28
31
  const fileId = request.params.id;
29
32
  const fileStorage = getFileStorage();
30
33
  try {
31
- // The file row decides existence (a scoped read, so it is also the access check).
34
+ // The file row decides existence (a scoped-or-reachable read, so it is also the access check).
32
35
  const file = await fileStorage.getFile(fileId);
33
36
  if (!file) {
34
37
  response.status(404).send('File not found');
@@ -0,0 +1,143 @@
1
+ import { SourceRepository } from '@proteinjs/reflection';
2
+ import { User } from '@proteinjs/user';
3
+ import { File } from '../src/tables/FileTable';
4
+ import { FileStorage } from '../src/FileStorage';
5
+ import { DbFileStorageDriver } from '../src/DbFileStorageDriver';
6
+ import { FileTestEnvironment } from './FileTestEnvironment';
7
+
8
+ /** Every byte value once — a payload that corrupts under any text/base64 mix-up. */
9
+ const rawBytes = Buffer.from(Array.from({ length: 256 }, (_, i) => i));
10
+ const rawBase64 = rawBytes.toString('base64');
11
+ /** A same-length replacement payload, byte-distinct at every position — the poisoning attempt. */
12
+ const poisonBytes = Buffer.from(Array.from({ length: 256 }, (_, i) => 255 - i));
13
+ const poisonBase64 = poisonBytes.toString('base64');
14
+
15
+ /**
16
+ * The byte-access gates on the browser-facing service (FileStorageService is `allUsers`, so
17
+ * these methods are every logged-in user's to call with any file id they can guess or leak):
18
+ *
19
+ * - **Reads** (`getFileData`): gated on the {@link FileStorage.getFile} row read — the same
20
+ * two-legged access decision every serving path derives from (the caller's SCOPED read, else
21
+ * the FileReachabilityResolver shared-content leg). Bytes are exactly as reachable as the row
22
+ * that names them. Ungated, a foreign file id read another user's bytes outright (the driver
23
+ * byte ops are deliberately unscoped — the access wall lives at this service boundary).
24
+ * - **Writes** (`updateFileData`): gated on the caller's SCOPED row read — owner scope is the
25
+ * write grant on ScopedRecords. Stricter than reads: the resolver leg widens READS only, so a
26
+ * share recipient who can read bytes still cannot write them. Ungated, a foreign file id let
27
+ * any caller poison another user's bytes (with the DbFileStorageDriver: caller-scoped chunk
28
+ * inserts landing beside the owner's — this suite runs that real driver so the intact-bytes
29
+ * assertions prove no chunk ever landed).
30
+ *
31
+ * The resolver is stubbed at the seam (content packages sit above db-file); denial is the named
32
+ * `File not found` — existence is not leaked to unauthorized callers.
33
+ */
34
+
35
+ const testEnv = new FileTestEnvironment();
36
+ type SourceRepositoryInternals = { objectCache: Record<string, unknown[]> };
37
+ const objectCache = () => (SourceRepository.get() as unknown as SourceRepositoryInternals).objectCache;
38
+
39
+ let owner: User;
40
+ let recipient: User;
41
+ let stranger: User;
42
+ /** File ids the stub resolver reports as reachable for the current user. */
43
+ const reachableFileIds = new Set<string>();
44
+ /** How many times the seam was consulted — the owner's scoped leg must never pay for it. */
45
+ let resolverConsultations = 0;
46
+
47
+ beforeAll(async () => {
48
+ await testEnv.beforeAll();
49
+ // The seam getFile consults on a scoped-read miss, stubbed (the real thought-media resolver
50
+ // lives above this package).
51
+ objectCache()['@proteinjs/db-file/FileReachabilityResolver'] = [
52
+ {
53
+ canReadViaReference: async (fileId: string) => {
54
+ resolverConsultations++;
55
+ return reachableFileIds.has(fileId);
56
+ },
57
+ },
58
+ ];
59
+ // The real chunked driver: write-gate bites are proven at the chunk-row level, not a mock.
60
+ testEnv.setDriver(new DbFileStorageDriver());
61
+ owner = await testEnv.createUser({ name: 'File owner', email: 'byte-owner@test.local' });
62
+ recipient = await testEnv.createUser({ name: 'Share recipient', email: 'byte-recipient@test.local' });
63
+ stranger = await testEnv.createUser({ name: 'Stranger', email: 'byte-stranger@test.local' });
64
+ testEnv.actAs(owner);
65
+ });
66
+
67
+ afterAll(async () => {
68
+ delete objectCache()['@proteinjs/db-file/FileReachabilityResolver'];
69
+ await testEnv.afterAll();
70
+ });
71
+
72
+ beforeEach(() => {
73
+ reachableFileIds.clear();
74
+ resolverConsultations = 0;
75
+ testEnv.actAs(owner);
76
+ });
77
+
78
+ const createOwnerFile = async (name: string): Promise<File> =>
79
+ await new FileStorage().createFile(
80
+ { name, type: 'application/octet-stream', size: rawBytes.length } as File,
81
+ rawBase64
82
+ );
83
+
84
+ describe('byte reads gate on the file-row read (the getFile decision)', () => {
85
+ it('the owner reads their bytes through the scoped leg alone — resolvers are never consulted', async () => {
86
+ const file = await createOwnerFile('own-bytes.bin');
87
+
88
+ const fileDataBase64 = await new FileStorage().getFileData(file.id);
89
+
90
+ expect(fileDataBase64).toEqual(rawBase64);
91
+ expect(resolverConsultations).toEqual(0);
92
+ });
93
+
94
+ it("THE VULN (read): a stranger holding only the file id is DENIED another user's bytes", async () => {
95
+ const file = await createOwnerFile('private-bytes.bin');
96
+
97
+ testEnv.actAs(stranger);
98
+ await expect(new FileStorage().getFileData(file.id)).rejects.toThrow(`File not found: ${file.id}`);
99
+ });
100
+
101
+ it('a shared-content recipient (resolver-vouched) reads the bytes byte-identical', async () => {
102
+ const file = await createOwnerFile('shared-bytes.bin');
103
+ reachableFileIds.add(file.id);
104
+
105
+ testEnv.actAs(recipient);
106
+ const fileDataBase64 = await new FileStorage().getFileData(file.id);
107
+
108
+ expect(fileDataBase64).toEqual(rawBase64);
109
+ });
110
+ });
111
+
112
+ describe('byte writes gate on the caller WRITING the row (owner scope; read-reachability never grants it)', () => {
113
+ it("THE VULN (write): a stranger holding only the file id cannot poison another user's bytes", async () => {
114
+ const file = await createOwnerFile('unpoisonable.bin');
115
+
116
+ testEnv.actAs(stranger);
117
+ await expect(new FileStorage().updateFileData(file.id, poisonBase64)).rejects.toThrow(`File not found: ${file.id}`);
118
+
119
+ // The outcome that matters: no chunk landed — the owner's bytes read back untouched.
120
+ testEnv.actAs(owner);
121
+ expect(await new FileStorage().getFileData(file.id)).toEqual(rawBase64);
122
+ });
123
+
124
+ it("read-reachability does NOT grant write: a vouched recipient's byte write is denied, bytes intact", async () => {
125
+ const file = await createOwnerFile('read-only-share.bin');
126
+ reachableFileIds.add(file.id);
127
+
128
+ testEnv.actAs(recipient);
129
+ expect(await new FileStorage().getFileData(file.id)).toEqual(rawBase64);
130
+ await expect(new FileStorage().updateFileData(file.id, poisonBase64)).rejects.toThrow(`File not found: ${file.id}`);
131
+
132
+ testEnv.actAs(owner);
133
+ expect(await new FileStorage().getFileData(file.id)).toEqual(rawBase64);
134
+ });
135
+
136
+ it('the owner replaces their bytes wholesale', async () => {
137
+ const file = await createOwnerFile('rewritable.bin');
138
+
139
+ await new FileStorage().updateFileData(file.id, poisonBase64);
140
+
141
+ expect(await new FileStorage().getFileData(file.id)).toEqual(poisonBase64);
142
+ });
143
+ });