@paroicms/server-image-cache-engine
Version:
The image variant engine that we use at Paroi.
356 lines • 11.6 kB
JavaScript
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