Skip to content

✨ Pre-warm shared ebook cache on book listing create and metadata sync#1266

Open
williamchong wants to merge 1 commit into
likecoin:masterfrom
williamchong:feature/book
Open

✨ Pre-warm shared ebook cache on book listing create and metadata sync#1266
williamchong wants to merge 1 commit into
likecoin:masterfrom
williamchong:feature/book

Conversation

@williamchong
Copy link
Copy Markdown
Member

When a book listing is created or its on-chain metadata/file path is refreshed, stream the referenced files into the GCS bucket that the ebook-cors service reads from, so first reads hit a warm cache.

The cache key resolution mirrors ebook-cors byte-for-byte (arweave links resolved locally via Firestore) so entries are actually hit. Runs fire-and-forget so it never blocks the response or the ISCN sync.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a fire-and-forget “cache warming” step that streams ebook files referenced by an NFT book’s on-chain metadata into the shared GCS cache bucket used by the ebook-cors service, so first reads are more likely to hit a warm cache.

Changes:

  • Invoke cache warming after NFT book listing creation (store route) and after on-chain metadata refresh (ISCN sync).
  • Introduce a new cacheBookFilesFromNFTClassMetadata() utility that resolves metadata URLs (including LikeCoin /arweave/v2/link/<txHash> indirection) and streams PDF/EPUB bytes directly into GCS.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/util/api/likernft/book/index.ts Triggers ebook-cache warming during ISCN metadata sync without blocking the sync flow.
src/util/api/likernft/book/cache.ts New implementation for resolving metadata file URLs, fetching with gateway fallbacks, and streaming into the shared GCS cache bucket.
src/routes/likernft/book/store.ts Triggers ebook-cache warming after successful book listing creation without blocking the HTTP response.

Comment on lines +48 to +79
async function resolveBookFileCacheURL(
targetURI: string,
): Promise<string | undefined> {
if (!targetURI) return undefined;
let parsedUrl: URL;
try {
parsedUrl = new URL(targetURI);
} catch {
return undefined;
}

if (targetURI.startsWith(LIKECOIN_API_LINK_PREFIX)) {
const txHash = targetURI
.slice(LIKECOIN_API_LINK_PREFIX.length)
.split('?')[0]
.split('/')[0];
if (!txHash) return undefined;
const tx = await getArweaveTxInfo(txHash);
if (!tx?.arweaveId) return undefined;
const link = new URL(`${ARWEAVE_GATEWAY}/${tx.arweaveId}`);
if (tx.key) link.searchParams.set('key', tx.key);
parsedUrl = link;
}

switch (parsedUrl.protocol) {
case 'ar:':
return `${ARWEAVE_GATEWAY}/${parsedUrl.host}`;
case 'ipfs:':
return `https://w3s.link/ipfs/${parsedUrl.host}`;
default:
return parsedUrl.toString();
}
Comment thread src/util/api/likernft/book/cache.ts Outdated
await pipeline(data, fileRef.createWriteStream({
metadata: {
contentType,
cacheControl: `${PERMANENT_CACHE_TIME_IN_S}`,
Comment on lines +171 to +175
await Promise.all(
targetURIs.map((targetURI) => cacheBookFile(classId, targetURI).catch((err) => {
// eslint-disable-next-line no-console
console.error(`Failed to cache book file for ${classId} (${targetURI}):`, err);
})),
Comment on lines +168 to +176
const targetURIs = Array.from(
new Set(getTargetURIsFromNFTClassMetadata(metadata)),
);
await Promise.all(
targetURIs.map((targetURI) => cacheBookFile(classId, targetURI).catch((err) => {
// eslint-disable-next-line no-console
console.error(`Failed to cache book file for ${classId} (${targetURI}):`, err);
})),
);
Comment on lines +40 to +80
/**
* Reproduces ebook-cors nft/index.js parseNFTMetadataURL() so the cache key we
* write is byte-for-byte identical to the one ebook-cors will look up.
*
* The api/arweave/v2/link/<txHash> case is resolved locally via Firestore
* (getArweaveTxInfo) instead of an HTTP round-trip — it yields the same
* `${ARWEAVE_GATEWAY}/${arweaveId}` (+ ?key=) link the endpoint returns.
*/
async function resolveBookFileCacheURL(
targetURI: string,
): Promise<string | undefined> {
if (!targetURI) return undefined;
let parsedUrl: URL;
try {
parsedUrl = new URL(targetURI);
} catch {
return undefined;
}

if (targetURI.startsWith(LIKECOIN_API_LINK_PREFIX)) {
const txHash = targetURI
.slice(LIKECOIN_API_LINK_PREFIX.length)
.split('?')[0]
.split('/')[0];
if (!txHash) return undefined;
const tx = await getArweaveTxInfo(txHash);
if (!tx?.arweaveId) return undefined;
const link = new URL(`${ARWEAVE_GATEWAY}/${tx.arweaveId}`);
if (tx.key) link.searchParams.set('key', tx.key);
parsedUrl = link;
}

switch (parsedUrl.protocol) {
case 'ar:':
return `${ARWEAVE_GATEWAY}/${parsedUrl.host}`;
case 'ipfs:':
return `https://w3s.link/ipfs/${parsedUrl.host}`;
default:
return parsedUrl.toString();
}
}
@williamchong williamchong force-pushed the feature/book branch 2 times, most recently from ed49e9e to 94a3d8c Compare May 20, 2026 19:45
When a book listing is created or its on-chain metadata/file path is
refreshed, stream the referenced files into the GCS bucket that the
ebook-cors service reads from, so first reads hit a warm cache.

The cache key resolution mirrors ebook-cors byte-for-byte (arweave links
resolved locally via Firestore) so entries are actually hit. Runs
fire-and-forget so it never blocks the response or the ISCN sync.
@williamchong williamchong marked this pull request as ready for review May 21, 2026 04:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants