@paroicms/server-image-cache-engine 0.25.0

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 (66) hide show
  1. package/README.md +3 -0
  2. package/ddl/image-cache.ddl.sql +32 -0
  3. package/dist/api.d.ts +6 -0
  4. package/dist/api.js +23 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/constants.d.ts +3 -0
  7. package/dist/constants.js +7 -0
  8. package/dist/constants.js.map +1 -0
  9. package/dist/database/PaCacheDependency.entity.d.ts +4 -0
  10. package/dist/database/PaCacheDependency.entity.js +30 -0
  11. package/dist/database/PaCacheDependency.entity.js.map +1 -0
  12. package/dist/database/PaImageCache.entity.d.ts +13 -0
  13. package/dist/database/PaImageCache.entity.js +71 -0
  14. package/dist/database/PaImageCache.entity.js.map +1 -0
  15. package/dist/database/PaImageCacheDependency.entity.d.ts +4 -0
  16. package/dist/database/PaImageCacheDependency.entity.js +30 -0
  17. package/dist/database/PaImageCacheDependency.entity.js.map +1 -0
  18. package/dist/database/PaImageCacheKey.entity.d.ts +5 -0
  19. package/dist/database/PaImageCacheKey.entity.js +35 -0
  20. package/dist/database/PaImageCacheKey.entity.js.map +1 -0
  21. package/dist/database/image-file-variant.entity.d.ts +13 -0
  22. package/dist/database/image-file-variant.entity.js +71 -0
  23. package/dist/database/image-file-variant.entity.js.map +1 -0
  24. package/dist/database/queries.d.ts +48 -0
  25. package/dist/database/queries.js +176 -0
  26. package/dist/database/queries.js.map +1 -0
  27. package/dist/db-init/db-init.d.ts +10 -0
  28. package/dist/db-init/db-init.js +35 -0
  29. package/dist/db-init/db-init.js.map +1 -0
  30. package/dist/db-init/ddl-migration.d.ts +12 -0
  31. package/dist/db-init/ddl-migration.js +51 -0
  32. package/dist/db-init/ddl-migration.js.map +1 -0
  33. package/dist/default-bo-favicon.d.ts +3 -0
  34. package/dist/default-bo-favicon.js +107 -0
  35. package/dist/default-bo-favicon.js.map +1 -0
  36. package/dist/engine-types.d.ts +45 -0
  37. package/dist/engine-types.js +3 -0
  38. package/dist/engine-types.js.map +1 -0
  39. package/dist/image-cache-engine.d.ts +48 -0
  40. package/dist/image-cache-engine.js +139 -0
  41. package/dist/image-cache-engine.js.map +1 -0
  42. package/dist/image-processor-setup.d.ts +8 -0
  43. package/dist/image-processor-setup.js +33 -0
  44. package/dist/image-processor-setup.js.map +1 -0
  45. package/dist/internal/compute-variant-size.d.ts +11 -0
  46. package/dist/internal/compute-variant-size.js +91 -0
  47. package/dist/internal/compute-variant-size.js.map +1 -0
  48. package/dist/internal/engine-context.d.ts +17 -0
  49. package/dist/internal/engine-context.js +26 -0
  50. package/dist/internal/engine-context.js.map +1 -0
  51. package/dist/internal/internal.types.d.ts +18 -0
  52. package/dist/internal/internal.types.js +3 -0
  53. package/dist/internal/internal.types.js.map +1 -0
  54. package/dist/internal/resizer.d.ts +20 -0
  55. package/dist/internal/resizer.js +67 -0
  56. package/dist/internal/resizer.js.map +1 -0
  57. package/dist/internal/task-processor.d.ts +2 -0
  58. package/dist/internal/task-processor.js +62 -0
  59. package/dist/internal/task-processor.js.map +1 -0
  60. package/dist/original-images-processing.d.ts +21 -0
  61. package/dist/original-images-processing.js +93 -0
  62. package/dist/original-images-processing.js.map +1 -0
  63. package/dist/public-helpers.d.ts +2 -0
  64. package/dist/public-helpers.js +8 -0
  65. package/dist/public-helpers.js.map +1 -0
  66. package/package.json +37 -0
@@ -0,0 +1,17 @@
1
+ import { PromiseToHandle } from "@paroi/async-lib";
2
+ import { AppLog } from "@paroicms/server-lib";
3
+ import { DataSource, EntityManager } from "typeorm";
4
+ import { ImageVariantWithBinary } from "../engine-types";
5
+ import type { CreateImageCacheEngineOptions } from "../image-cache-engine";
6
+ import { TaskData, TaskKey } from "./internal.types";
7
+ export interface VariantEngineContext extends Omit<CreateImageCacheEngineOptions, "appLog"> {
8
+ status: "ready" | "destroyed";
9
+ appLog: AppLog;
10
+ queue: TaskData[];
11
+ pendingTasks: Map<TaskKey, PromiseToHandle<ImageVariantWithBinary>>;
12
+ mainCn: DataSource;
13
+ cn: EntityManager;
14
+ logNextQuery: (count?: number) => void;
15
+ timeToIdleDays?: number;
16
+ }
17
+ export declare function createVariantEngineContext(options: CreateImageCacheEngineOptions): Promise<VariantEngineContext>;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createVariantEngineContext = void 0;
4
+ const anywhere_lib_1 = require("@paroicms/anywhere-lib");
5
+ const db_init_1 = require("../db-init/db-init");
6
+ async function createVariantEngineContext(options) {
7
+ const appLog = options.appLog ?? console;
8
+ const { mainCn, logNextQuery } = await (0, db_init_1.createOrOpenMainCn)({
9
+ siteName: options.siteName,
10
+ sqliteFile: options.storage.file,
11
+ appLog,
12
+ });
13
+ return {
14
+ ...options,
15
+ status: "ready",
16
+ appLog,
17
+ queue: [],
18
+ pendingTasks: new Map(),
19
+ mainCn,
20
+ cn: mainCn.manager,
21
+ logNextQuery,
22
+ timeToIdleDays: options.timeToIdle === undefined ? undefined : (0, anywhere_lib_1.parseTimeWithUnit)(options.timeToIdle).delay,
23
+ };
24
+ }
25
+ exports.createVariantEngineContext = createVariantEngineContext;
26
+ //# sourceMappingURL=engine-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-context.js","sourceRoot":"","sources":["../../src/internal/engine-context.ts"],"names":[],"mappings":";;;AACA,yDAA2D;AAG3D,gDAAwD;AAgBjD,KAAK,UAAU,0BAA0B,CAC9C,OAAsC;IAEtC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC;IAEzC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,IAAA,4BAAkB,EAAC;QACxD,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI;QAChC,MAAM;KACP,CAAC,CAAC;IAEH,OAAO;QACL,GAAG,OAAO;QACV,MAAM,EAAE,OAAO;QACf,MAAM;QACN,KAAK,EAAE,EAAE;QACT,YAAY,EAAE,IAAI,GAAG,EAAE;QACvB,MAAM;QACN,EAAE,EAAE,MAAM,CAAC,OAAO;QAClB,YAAY;QACZ,cAAc,EACZ,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAA,gCAAiB,EAAC,OAAO,CAAC,UAAU,CAAC,CAAC,KAAK;KAC7F,CAAC;AACJ,CAAC;AAvBD,gEAuBC"}
@@ -0,0 +1,18 @@
1
+ import { CompleteVariantName } from "../engine-types";
2
+ export interface TaskData {
3
+ taskKey: TaskKey;
4
+ variantName: CompleteVariantName;
5
+ imageSize: ImageSize;
6
+ imageUid: string;
7
+ mediaType: string;
8
+ changeMediaTypeTo?: string;
9
+ dependsOnCacheKey: string;
10
+ }
11
+ export interface ImageSize {
12
+ width: number;
13
+ height: number;
14
+ }
15
+ /**
16
+ * Format is: {imageUid}:{completeVariantName}
17
+ */
18
+ export type TaskKey = `${string}:${CompleteVariantName}`;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=internal.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.types.js","sourceRoot":"","sources":["../../src/internal/internal.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ /// <reference types="node" />
2
+ import { ImageCompressionQualityPolicy } from "@paroicms/anywhere-lib";
3
+ import { ImageVariantWithBinary } from "../engine-types";
4
+ import { ImageSize } from "./internal.types";
5
+ export type UnsavedImageVariantWithBinary = Omit<ImageVariantWithBinary, "lastModified">;
6
+ export declare function resizeImage(options: {
7
+ binaryFile: Buffer;
8
+ imageSize: ImageSize;
9
+ formatTo?: {
10
+ mediaType: string;
11
+ lossless?: boolean;
12
+ };
13
+ qualityPolicy: ImageCompressionQualityPolicy[];
14
+ }): Promise<UnsavedImageVariantWithBinary>;
15
+ export declare function resizeIcoImage(options: {
16
+ binaryFile: Buffer;
17
+ imageSize: ImageSize;
18
+ }): Promise<UnsavedImageVariantWithBinary>;
19
+ export declare function convertSharpFormatToMediaType(format: string): string;
20
+ export declare function getQuality({ width, height }: ImageSize, qualityPolicy?: ImageCompressionQualityPolicy[]): number;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getQuality = exports.convertSharpFormatToMediaType = exports.resizeIcoImage = exports.resizeImage = void 0;
7
+ const sharp_1 = __importDefault(require("sharp"));
8
+ const constants_1 = require("../constants");
9
+ const image_processor_setup_1 = require("../image-processor-setup");
10
+ const icoEndec = require("ico-endec");
11
+ async function resizeImage(options) {
12
+ if (options.formatTo?.mediaType === "image/x-icon")
13
+ return await resizeIcoImage(options);
14
+ const sharpFormat = options.formatTo
15
+ ? convertMediaTypeToSharpFormat(options.formatTo.mediaType)
16
+ : "webp";
17
+ const sharpInst = (0, sharp_1.default)(options.binaryFile)
18
+ .resize(options.imageSize)
19
+ .toFormat(sharpFormat, {
20
+ quality: getQuality(options.imageSize),
21
+ lossless: options.formatTo?.lossless,
22
+ });
23
+ const { data, info } = await (0, image_processor_setup_1.sharpQueue)(() => sharpInst.toBuffer({ resolveWithObject: true }));
24
+ return {
25
+ width: info.width,
26
+ height: info.height,
27
+ weightB: info.size,
28
+ mediaType: convertSharpFormatToMediaType(info.format),
29
+ binaryFile: data,
30
+ };
31
+ }
32
+ exports.resizeImage = resizeImage;
33
+ async function resizeIcoImage(options) {
34
+ const { data, info } = await (0, image_processor_setup_1.sharpQueue)(() => (0, sharp_1.default)(options.binaryFile)
35
+ .resize(options.imageSize)
36
+ .toFormat("png")
37
+ .toBuffer({ resolveWithObject: true }));
38
+ const binaryFile = icoEndec.encode([data]);
39
+ return {
40
+ width: info.width,
41
+ height: info.height,
42
+ weightB: info.size,
43
+ mediaType: "image/x-icon",
44
+ binaryFile,
45
+ };
46
+ }
47
+ exports.resizeIcoImage = resizeIcoImage;
48
+ function convertMediaTypeToSharpFormat(mediaType) {
49
+ if (!mediaType.startsWith("image/") || !constants_1.authorizedMediaTypes.has(mediaType)) {
50
+ throw new Error(`invalid media-type '${mediaType}'`);
51
+ }
52
+ return mediaType.substring(6);
53
+ }
54
+ function convertSharpFormatToMediaType(format) {
55
+ return `image/${format}`;
56
+ }
57
+ exports.convertSharpFormatToMediaType = convertSharpFormatToMediaType;
58
+ function getQuality({ width, height }, qualityPolicy) {
59
+ const area = width * height;
60
+ for (const { quality, areaLimitPx } of qualityPolicy ?? []) {
61
+ if (areaLimitPx === undefined || area <= areaLimitPx)
62
+ return quality;
63
+ }
64
+ return 70;
65
+ }
66
+ exports.getQuality = getQuality;
67
+ //# sourceMappingURL=resizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizer.js","sourceRoot":"","sources":["../../src/internal/resizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { VariantEngineContext } from "./engine-context";
2
+ export declare function processAllPending(context: VariantEngineContext): Promise<void>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processAllPending = void 0;
4
+ const queries_1 = require("../database/queries");
5
+ const resizer_1 = require("./resizer");
6
+ async function processAllPending(context) {
7
+ while (true) {
8
+ const task = context.queue.shift();
9
+ if (!task)
10
+ break;
11
+ const pth = context.pendingTasks.get(task.taskKey);
12
+ if (!pth) {
13
+ context.appLog.error(`Missing pending variant '${task.taskKey}'`);
14
+ continue;
15
+ }
16
+ try {
17
+ const result = await excecuteTask(task, context);
18
+ pth.resolve(result);
19
+ }
20
+ catch (error) {
21
+ context.appLog.error(`[image-cache-engine] image '${task.imageUid}', '${task.variantName}':`, error, task);
22
+ pth.reject(error);
23
+ }
24
+ context.pendingTasks.delete(task.taskKey);
25
+ }
26
+ }
27
+ exports.processAllPending = processAllPending;
28
+ async function excecuteTask(task, ctx) {
29
+ const found = await (0, queries_1.fetchImageVariantWithBinary)(ctx, {
30
+ imageUid: task.imageUid,
31
+ variantName: task.variantName,
32
+ });
33
+ if (found) {
34
+ await (0, queries_1.markOneCacheKeyAsUsed)(ctx, {
35
+ imageCacheId: found.id,
36
+ cacheKey: task.dependsOnCacheKey,
37
+ });
38
+ return found.variant;
39
+ }
40
+ const origBinaryFile = await ctx.getOriginalImage(task.imageUid);
41
+ const result = await (0, resizer_1.resizeImage)({
42
+ binaryFile: origBinaryFile,
43
+ imageSize: task.imageSize,
44
+ formatTo: task.changeMediaTypeTo
45
+ ? {
46
+ mediaType: task.changeMediaTypeTo,
47
+ }
48
+ : undefined,
49
+ qualityPolicy: ctx.qualityPolicy,
50
+ });
51
+ const { lastModified } = await (0, queries_1.insertIntoImageVariant)(ctx.cn, {
52
+ imageUid: task.imageUid,
53
+ mediaType: result.mediaType,
54
+ variantName: task.variantName,
55
+ width: result.width,
56
+ height: result.height,
57
+ weightB: result.weightB,
58
+ binaryFile: result.binaryFile,
59
+ }, task.dependsOnCacheKey);
60
+ return { ...result, lastModified };
61
+ }
62
+ //# sourceMappingURL=task-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-processor.js","sourceRoot":"","sources":["../../src/internal/task-processor.ts"],"names":[],"mappings":";;;AAAA,iDAI6B;AAI7B,uCAAwC;AAEjC,KAAK,UAAU,iBAAiB,CAAC,OAA6B;IACnE,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI;YAAE,MAAM;QAEjB,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEnD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;YAClE,SAAS;QACX,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAEjD,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+BAA+B,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,WAAW,IAAI,EACvE,KAAK,EACL,IAAI,CACL,CAAC;YACF,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QAED,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AA3BD,8CA2BC;AAED,KAAK,UAAU,YAAY,CACzB,IAAc,EACd,GAAyB;IAEzB,MAAM,KAAK,GAAG,MAAM,IAAA,qCAA2B,EAAC,GAAG,EAAE;QACnD,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,WAAW,EAAE,IAAI,CAAC,WAAW;KAC9B,CAAC,CAAC;IACH,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,IAAA,+BAAqB,EAAC,GAAG,EAAE;YAC/B,YAAY,EAAE,KAAK,CAAC,EAAE;YACtB,QAAQ,EAAE,IAAI,CAAC,iBAAiB;SACjC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG,MAAM,IAAA,qBAAW,EAAC;QAC/B,UAAU,EAAE,cAAc;QAC1B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,iBAAiB;YAC9B,CAAC,CAAC;gBACE,SAAS,EAAE,IAAI,CAAC,iBAAiB;aAClC;YACH,CAAC,CAAC,SAAS;QACb,aAAa,EAAE,GAAG,CAAC,aAAa;KACjC,CAAC,CAAC;IAEH,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,IAAA,gCAAsB,EACnD,GAAG,CAAC,EAAE,EACN;QACE,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,EACD,IAAI,CAAC,iBAAiB,CACvB,CAAC;IAEF,OAAO,EAAE,GAAG,MAAM,EAAE,YAAY,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /// <reference types="node" />
2
+ import { ImageCompressionQualityPolicy, ImagePolicy } from "@paroicms/anywhere-lib";
3
+ export interface BadRequestErrorType {
4
+ new (message?: string): Error;
5
+ }
6
+ export interface OriginalImage {
7
+ binaryFile: Buffer;
8
+ width: number;
9
+ height: number;
10
+ weightB: number;
11
+ mediaType: string;
12
+ lossless: boolean | undefined;
13
+ }
14
+ export declare function processOriginalImage({ binaryFile, mediaType, BadRequestError, policy, defaultPolicy, qualityPolicy, }: {
15
+ binaryFile: Buffer;
16
+ mediaType: string;
17
+ BadRequestError: BadRequestErrorType;
18
+ policy?: ImagePolicy;
19
+ defaultPolicy: ImagePolicy;
20
+ qualityPolicy: ImageCompressionQualityPolicy[];
21
+ }): Promise<OriginalImage>;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.processOriginalImage = void 0;
7
+ const sharp_1 = __importDefault(require("sharp"));
8
+ const constants_1 = require("./constants");
9
+ const image_processor_setup_1 = require("./image-processor-setup");
10
+ const resizer_1 = require("./internal/resizer");
11
+ async function processOriginalImage({ binaryFile, mediaType, BadRequestError, policy, defaultPolicy, qualityPolicy, }) {
12
+ const weightB = binaryFile.byteLength;
13
+ const sharpInst = (0, sharp_1.default)(binaryFile);
14
+ const { width, height, format } = await (0, image_processor_setup_1.sharpQueue)(() => sharpInst.metadata());
15
+ if (width === undefined || height === undefined || !format) {
16
+ throw new Error("cannot get image metadata");
17
+ }
18
+ const foundMediaType = (0, resizer_1.convertSharpFormatToMediaType)(format);
19
+ if (foundMediaType !== mediaType) {
20
+ throw new BadRequestError(`mismatch media type '${mediaType}' with '${foundMediaType}'`);
21
+ }
22
+ if (!constants_1.authorizedMediaTypes.has(mediaType)) {
23
+ throw new BadRequestError("Invalid image type");
24
+ }
25
+ const weightLimitB = policy?.weightLimitB ?? defaultPolicy.weightLimitB;
26
+ const areaLimitPx = policy?.areaLimitPx ?? defaultPolicy.areaLimitPx;
27
+ if (mediaType === "image/gif" || mediaType === "image/svg+xml") {
28
+ if (weightLimitB && weightB > weightLimitB) {
29
+ throw new BadRequestError("Image weight exceeds limit");
30
+ }
31
+ return { binaryFile, width, height, mediaType, weightB, lossless: undefined };
32
+ }
33
+ const input = {
34
+ width,
35
+ height,
36
+ mediaType,
37
+ sharpInst,
38
+ };
39
+ if (mediaType === "image/webp") {
40
+ if (weightLimitB === undefined || weightB <= weightLimitB) {
41
+ return { binaryFile, width, height, mediaType, weightB, lossless: undefined };
42
+ }
43
+ return await convertToWebp({ input, qualityPolicy, areaLimitPx, lossless: false });
44
+ }
45
+ if (mediaType === "image/png") {
46
+ return await convertToWebp({
47
+ input,
48
+ qualityPolicy,
49
+ areaLimitPx,
50
+ // WebP lossless images are 26% smaller in size compared to PNGs
51
+ // See: https://developers.google.com/speed/webp
52
+ lossless: weightLimitB === undefined || weightB <= weightLimitB * 1.35,
53
+ });
54
+ }
55
+ return await convertToWebp({ input, qualityPolicy, areaLimitPx, lossless: false });
56
+ }
57
+ exports.processOriginalImage = processOriginalImage;
58
+ async function convertToWebp({ input, qualityPolicy, areaLimitPx, lossless, }) {
59
+ const { width, height } = areaLimitPx === undefined ? input : computeOriginalSize(input, areaLimitPx);
60
+ const size = { width, height }; // to be sure we keep only these properties
61
+ const quality = (0, resizer_1.getQuality)(size, qualityPolicy);
62
+ const { data, info } = await (0, image_processor_setup_1.sharpQueue)(() => input.sharpInst
63
+ .resize(size)
64
+ .webp({
65
+ lossless,
66
+ quality,
67
+ })
68
+ .toBuffer({ resolveWithObject: true }));
69
+ // we keep it here for the moment
70
+ // console.log(
71
+ // `....convertToWebp: ${input.width}x${input.height} => ${info.width}x${info.height}, ${
72
+ // info.size / 1000
73
+ // }KB`,
74
+ // );
75
+ return {
76
+ width: info.width,
77
+ height: info.height,
78
+ weightB: info.size,
79
+ mediaType: "image/webp",
80
+ lossless,
81
+ binaryFile: data,
82
+ };
83
+ }
84
+ function computeOriginalSize(input, areaLimitPx) {
85
+ const inputArea = input.width * input.height;
86
+ if (inputArea <= areaLimitPx)
87
+ return input;
88
+ const ratio = input.width / input.height;
89
+ const width = Math.trunc(Math.sqrt(areaLimitPx * ratio));
90
+ const height = Math.round((input.height / input.width) * width);
91
+ return { width, height };
92
+ }
93
+ //# sourceMappingURL=original-images-processing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"original-images-processing.js","sourceRoot":"","sources":["../src/original-images-processing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { VariantName } from "./engine-types";
2
+ export declare function isVariantName(variantName: string): variantName is VariantName;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isVariantName = void 0;
4
+ function isVariantName(variantName) {
5
+ return /^([0-9]+x|x[0-9]+|x[0-9]+x|[0-9]+x[0-9]+)$/.test(variantName);
6
+ }
7
+ exports.isVariantName = isVariantName;
8
+ //# sourceMappingURL=public-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-helpers.js","sourceRoot":"","sources":["../src/public-helpers.ts"],"names":[],"mappings":";;;AAEA,SAAgB,aAAa,CAAC,WAAmB;IAC/C,OAAO,4CAA4C,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACxE,CAAC;AAFD,sCAEC"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@paroicms/server-image-cache-engine",
3
+ "version": "0.25.0",
4
+ "description": "The image variant engine that we use at Paroi.",
5
+ "author": "Paroi Team",
6
+ "main": "dist/api.js",
7
+ "keywords": [],
8
+ "license": "MIT",
9
+ "typings": "dist/api.d.ts",
10
+ "type": "commonjs",
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "clear": "rimraf dist/*",
14
+ "dev": "tsc --watch --preserveWatchOutput",
15
+ "eslint": "eslint ."
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "~20.10.5",
19
+ "rimraf": "~5.0.5",
20
+ "typescript": "~5.3.3"
21
+ },
22
+ "dependencies": {
23
+ "@paroi/async-lib": "~0.1.2",
24
+ "@paroi/data-formatters-lib": "~0.1.2",
25
+ "@paroicms/server-lib": "0.14.0",
26
+ "@paroicms/anywhere-lib": "0.53.0",
27
+ "ico-endec": "~0.1.6",
28
+ "sharp": "~0.33.1"
29
+ },
30
+ "peerDependencies": {
31
+ "typeorm": "0.3"
32
+ },
33
+ "files": [
34
+ "ddl",
35
+ "dist"
36
+ ]
37
+ }