mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
[monorepo] read empty content from its hash, in a shared blob store base (#36831)
The hash of empty content is a property of the content rather than of anything stored: every project's empty file has it, whether or not a blob was ever written under it, and a file may reference it before anything has. Eight blob stores were each paying a lookup -- a database query, an HTTP request, a read of a cache that offline mode may be the only thing standing in for -- to be told what the hash already says, and clsi had already reinvented the answer on its own. overleaf-editor-core gains BlobStoreBase, which answers getString for that hash and delegates the rest to the fetchString its subclasses provide. getObject comes with it: four stores spelled out the same JSON.parse of getString, and a fifth wrapped it in a log line. Adopted by history-v1's BlobStore, BackupBlobStore, HashCheckBlobStore and recover_zip's RecoveryBlobStore, by project-history's BlobStore, by clsi's BlobStore, and by web's HistoryBlobStore, SimpleBlobStore and FrontendBlobStore. history-v1 keeps its own answers in getStream, getBlob and getBlobs, which no other store implements. Writing and copying a blob are untouched, so a project that wrote empty content still has a blob for it and a read answers the same either way. Two paths could not handle a project holding a tracked change or a comment at all. Loading such a file needs the ranges blob its rangesHash points at, and hashing one again needs to write it back, so a store missing getObject or putObject fails outright: - recover_zip died on `blobStore.getObject is not a function`, making every such project unrecoverable. The test covering it asserts a tracked delete is absent from the zip, which the script already asked for and never reached. - GET /projects/:project_id/latest/hashed_content returned 500 from the same cause, then would have hit a missing putObject in snapshot.store. HashCheckBlobStore becomes a class over the base and gains putObject. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> GitOrigin-RevId: c84f62465beff6cf77ffac26aa772984e1b554cb
This commit is contained in:
committed by
Copybot
co-authored by
Claude Opus 5
parent
7608d738a6
commit
6caa5fc3a3
@@ -0,0 +1,68 @@
|
||||
// @ts-check
|
||||
'use strict'
|
||||
|
||||
const Blob = require('./blob')
|
||||
|
||||
/**
|
||||
* The parts of reading a blob that every blob store answers the same way.
|
||||
*
|
||||
* The hash of empty content is a property of the content rather than of anything
|
||||
* stored: every project's empty file has it, whether or not anything ever wrote a
|
||||
* blob under it, and a file may reference it before anything has. So no store has
|
||||
* to look it up, and every store that did was paying a lookup -- a database query,
|
||||
* an HTTP request, a read of a cache that offline mode may be the only thing
|
||||
* standing in for -- to be told what the hash already says. Deserializing a blob
|
||||
* that holds JSON is likewise the same wherever it is read from.
|
||||
*
|
||||
* Implementations extend this and provide `fetchString`, which is only ever called
|
||||
* for a hash that needs fetching. Spelling either of these out in each store
|
||||
* instead is a line per store, and the store written next is the one that forgets.
|
||||
*
|
||||
* `getObject` inherits the empty-hash answer through `getString`, so it parses `''`
|
||||
* and fails. That is right: a ranges blob is written from `JSON.stringify`, which
|
||||
* never yields empty content, so a hash reaching `getObject` is one something
|
||||
* stored. A store that reads or repairs its JSON differently overrides it.
|
||||
*/
|
||||
class BlobStoreBase {
|
||||
/**
|
||||
* Fetch a blob's content as a string.
|
||||
*
|
||||
* @param {string} hash hexadecimal SHA-1 hash
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
async getString(hash) {
|
||||
if (hash === Blob.EMPTY_HASH) {
|
||||
return ''
|
||||
}
|
||||
return await this.fetchString(hash)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a blob holding JSON and deserialize it.
|
||||
*
|
||||
* @template [T=unknown]
|
||||
* @param {string} hash hexadecimal SHA-1 hash
|
||||
* @return {Promise<T>}
|
||||
*/
|
||||
async getObject(hash) {
|
||||
return /** @type {T} */ (JSON.parse(await this.getString(hash)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a blob's content as a string from wherever this store keeps it.
|
||||
*
|
||||
* Called only for a hash that something needs to have stored, so an
|
||||
* implementation may treat a miss as an error.
|
||||
*
|
||||
* @abstract
|
||||
* @param {string} hash hexadecimal SHA-1 hash
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
async fetchString(hash) {
|
||||
throw new Error(
|
||||
`${this.constructor.name} does not implement fetchString(hash)`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlobStoreBase
|
||||
Reference in New Issue
Block a user