UNPKG

@paroicms/server-image-cache-engine

Version:

The image variant engine that we use at Paroi.

356 lines 11.6 kB
import { dateVal, isDef, nbVal, strVal, strValOrUndef } from "@paroi/data-formatters-lib"; export async function insertIntoImageVariant(ctx, data, owners) { const { cn } = ctx; const mTypeRSize = makeMTypeRSizeKey(data); const existingRow = await cn("PaImageCache") .select("id") .where({ mediaId: data.mediaId, mTypeRSize, }) .first(); let imageCacheId = existingRow ? strVal(existingRow.id) : undefined; let modifiedAt; if (imageCacheId === undefined) { const [inserted] = await cn("PaImageCache") .insert({ resourceVersion: data.resourceVersion, mediaId: data.mediaId, mTypeRSize, mediaType: data.mediaType, rawWidth: data.rawWidth, rawHeight: data.rawHeight, weightB: data.weightB, binaryFile: data.binaryFile, slug: data.slug, modifiedAt: cn.fn.now(), }) .returning(["id", "modifiedAt"]); imageCacheId = strVal(inserted.id); modifiedAt = dateVal(inserted.modifiedAt); } else { await cn("PaImageCache") .where({ id: imageCacheId, }) .update({ weightB: data.weightB, binaryFile: data.binaryFile, modifiedAt: cn.fn.now(), }); const row = await cn("PaImageCache") .select("modifiedAt") .where("id", imageCacheId) .first(); if (!row) throw new Error(`missing cache row: ${imageCacheId}`); modifiedAt = dateVal(row.modifiedAt); } await insertIntoPaImageCacheOwner(ctx, { imageCacheId, owners }); return { imageCacheId, lastModified: modifiedAt.toISOString() }; } export async function insertIntoPaImageCacheOwner(ctx, input) { const { cn } = ctx; const { imageCacheId, owners } = input; if (owners.length === 0) return; for (const { ownerHandle, rawResizeR } of owners) { await cn("PaImageCacheOwner") .insert({ imageCacheId, ownerHandle, rawResizeR, }) .onConflict(["imageCacheId", "ownerHandle", "rawResizeR"]) .ignore(); } } export async function fetchImageVariant(ctx, input) { const { mediaId, rawWidth, rawHeight } = input; const row = await ctx .cn("PaImageCache as v") .select([ "v.id", "v.mediaType", "v.rawWidth", "v.rawHeight", "v.modifiedAt", "v.resourceVersion", "v.slug", ]) .where({ "v.mediaId": mediaId, "v.mTypeRSize": makeMTypeRSizeKey(input), }) .first(); if (!row) return; const id = strVal(row.id); return { id, variant: { mediaId, resourceVersion: strVal(row.resourceVersion), slug: strValOrUndef(row.slug), mediaType: strVal(row.mediaType), rawWidth: nbVal(row.rawWidth), rawHeight: nbVal(row.rawHeight), rawSizeName: `${rawWidth}x${rawHeight}`, }, }; } export async function fetchImageVariantWithBinary(ctx, input) { const { mediaId } = input; const row = await ctx .cn("PaImageCache as v") .select([ "v.id", "v.resourceVersion", "v.rawWidth", "v.rawHeight", "v.mediaType", "v.weightB", "v.binaryFile", "v.modifiedAt", "v.slug", ]) .where({ "v.mediaId": mediaId, "v.mTypeRSize": makeMTypeRSizeKey(input), }) .first(); if (!row) return; const id = strVal(row.id); const rawWidth = nbVal(row.rawWidth); const rawHeight = nbVal(row.rawHeight); return { id, variant: { mediaId, resourceVersion: strVal(row.resourceVersion), slug: strValOrUndef(row.slug), mediaType: strVal(row.mediaType), rawWidth, rawHeight, weightB: nbVal(row.weightB), binaryFile: row.binaryFile, lastModified: dateVal(row.modifiedAt).toISOString(), rawSizeName: `${rawWidth}x${rawHeight}`, }, }; } export async function fetchImageVariantWithBinaryByOwner(ctx, input) { const { mediaId, mediaType, ownerHandle, rawResizeR } = input; const row = await ctx .cn("PaImageCache as v") .innerJoin("PaImageCacheOwner as o", "o.imageCacheId", "v.id") .select([ "v.id", "v.mediaId", "v.resourceVersion", "v.rawWidth", "v.rawHeight", "v.mediaType", "v.weightB", "v.binaryFile", "v.modifiedAt", "v.slug", ]) .where({ "v.mediaId": mediaId, "v.mediaType": mediaType, "o.ownerHandle": ownerHandle, "o.rawResizeR": rawResizeR, }) .first(); if (!row) return; const id = strVal(row.id); const rawWidth = nbVal(row.rawWidth); const rawHeight = nbVal(row.rawHeight); return { id, variant: { mediaId: strVal(row.mediaId), resourceVersion: strVal(row.resourceVersion), slug: strValOrUndef(row.slug), mediaType: strVal(row.mediaType), rawWidth, rawHeight, weightB: nbVal(row.weightB), binaryFile: row.binaryFile, lastModified: dateVal(row.modifiedAt).toISOString(), rawSizeName: `${rawWidth}x${rawHeight}`, }, }; } export async function deleteVariants(ctx, options) { const { cn } = ctx; if ("mediaIds" in options) { await cn("PaImageCache").whereIn("mediaId", options.mediaIds).delete(); } else { await cn("PaImageCache").where("mediaId", options.mediaId); } } /** * @returns the list of cache ownerHandles that were deleted */ export async function deleteVariantsByDependencies(ctx, options) { const { cn } = ctx; await cn("PaImageCacheOwner as o").whereIn("o.ownerHandle", options.ownerHandles).delete(); await deleteOrphanVariants(ctx); } /** * Delete the images that have no ownerHandle and that have not been used recently. * @returns the number of cache identifiers that were deleted */ export async function deleteOrphanVariants(ctx) { const { cn } = ctx; // Find records to delete const subquery = cn("PaImageCacheOwner as k") .select(1) .whereRaw("k.imageCacheId = PaImageCache.id"); const result = await cn("PaImageCache") .whereNotExists(subquery) .andWhereRaw(`date(modifiedAt) < date(current_timestamp, '-7 day')`) .delete(); return result; } export async function associateOwnerToImages(ctx, { ownerHandle, cacheImages, isHandleReusable, }) { const { cn } = ctx; await cn.transaction(async (tx) => { const prevRows = await tx("PaImageCacheOwner") .select(["imageCacheId", "rawResizeR"]) .where({ ownerHandle }); const prevOwnedItems = new Map(prevRows.map((row) => { const item = { imageCacheId: strVal(row.imageCacheId), rawResizeR: strVal(row.rawResizeR), }; return [makeCacheImageKey(item), item]; })); const newCacheImages = new Map(cacheImages.map((i) => [makeCacheImageKey(i), i.rawResizeR])); // Insert new items const toInsert = cacheImages.filter((item) => !prevOwnedItems.has(makeCacheImageKey(item))); if (toInsert.length > 0) { await tx("PaImageCacheOwner").insert(toInsert.map(({ imageCacheId, rawResizeR }) => ({ imageCacheId, ownerHandle, rawResizeR, }))); } // Delete obsolete items if (!isHandleReusable) { const toDelete = Array.from(prevOwnedItems.entries()) .filter(([key]) => !newCacheImages.has(key)) .map(([_, item]) => item); if (toDelete.length > 0) { const query = tx("PaImageCacheOwner").where({ ownerHandle }); for (const item of toDelete) { query.orWhere({ imageCacheId: item.imageCacheId, rawResizeR: item.rawResizeR, }); } await query.delete(); } } }); } function makeCacheImageKey({ imageCacheId, rawResizeR, }) { return `${imageCacheId}:${rawResizeR}`; } export async function fetchStoredCacheItems(ctx, ownerHandle) { const { cn } = ctx; const query = cn("PaImageCache as v") .select([ "v.id", "v.mediaId", "v.mediaType", "v.rawWidth", "v.rawHeight", "v.modifiedAt", "v.resourceVersion", "v.slug", ]) .join("PaImageCacheOwner as o", "o.imageCacheId", "v.id") .where("o.ownerHandle", ownerHandle); const rows = await query; return rows.map((row) => ({ imageCacheId: strVal(row.id), variant: formatImageVariant(row), alreadyOwned: true, })); } export async function fetchBatchOfStoredCacheItems(ctx, groups, alreadyOwnedBy) { if (groups.length === 0) return []; const { cn } = ctx; const query = cn("PaImageCache as v") .select([ "v.id", "v.mediaId", "v.mediaType", "v.rawWidth", "v.rawHeight", "v.modifiedAt", "v.resourceVersion", "v.slug", "o.ownerHandle", ]) .leftJoin("PaImageCacheOwner as o", { "o.imageCacheId": "v.id", "o.ownerHandle": cn.raw("?", [alreadyOwnedBy]), }); query.where((qb) => { for (const { mediaIds, requestedVariants } of groups) { if (mediaIds.length === 0 || requestedVariants.length === 0) continue; qb.orWhere((subQb) => { subQb.whereIn("v.mediaId", mediaIds).whereIn("v.mTypeRSize", requestedVariants.map((v) => makeMTypeRSizeKey(v))); }); } }); const rows = await query; return rows.map((row) => ({ imageCacheId: strVal(row.id), variant: formatImageVariant(row), alreadyOwned: isDef(row.ownerHandle), })); } export async function clearAll({ cn }) { await cn("PaImageCacheOwner").delete(); await cn("PaImageCache").delete(); } function makeMTypeRSizeKey(image) { return `${image.rawWidth}x${image.rawHeight}:${image.mediaType}`; } function formatImageVariant(row) { return { mediaId: strVal(row.mediaId), resourceVersion: strVal(row.resourceVersion), slug: strValOrUndef(row.slug), mediaType: strVal(row.mediaType), rawWidth: nbVal(row.rawWidth), rawHeight: nbVal(row.rawHeight), rawSizeName: `${nbVal(row.rawWidth)}x${nbVal(row.rawHeight)}`, }; } export async function fetchUsedRawSizesByMedia(ctx, mediaId) { const { cn } = ctx; const rows = await cn("PaImageCache as v") .innerJoin("PaImageCacheOwner as o", "o.imageCacheId", "v.id") .select(["o.rawResizeR", "v.rawWidth", "v.rawHeight"]) .where("v.mediaId", mediaId) .orderBy("v.rawWidth", "asc") .orderBy("v.rawHeight", "asc") .distinct(); return rows.map((row) => ({ rawResizeR: strVal(row.rawResizeR), rawSizeName: `${nbVal(row.rawWidth)}x${nbVal(row.rawHeight)}`, })); } //# sourceMappingURL=queries.js.map