@openinary/core 1.1.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.
- package/LICENSE +661 -0
- package/dist/config/deps.d.ts +14 -0
- package/dist/config/deps.d.ts.map +1 -0
- package/dist/config/deps.js +2 -0
- package/dist/config/deps.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/authenticated.d.ts +4 -0
- package/dist/routes/authenticated.d.ts.map +1 -0
- package/dist/routes/authenticated.js +130 -0
- package/dist/routes/authenticated.js.map +1 -0
- package/dist/routes/download-folder.d.ts +4 -0
- package/dist/routes/download-folder.d.ts.map +1 -0
- package/dist/routes/download-folder.js +101 -0
- package/dist/routes/download-folder.js.map +1 -0
- package/dist/routes/download-zip.d.ts +15 -0
- package/dist/routes/download-zip.d.ts.map +1 -0
- package/dist/routes/download-zip.js +147 -0
- package/dist/routes/download-zip.js.map +1 -0
- package/dist/routes/download.d.ts +4 -0
- package/dist/routes/download.d.ts.map +1 -0
- package/dist/routes/download.js +83 -0
- package/dist/routes/download.js.map +1 -0
- package/dist/routes/invalidate.d.ts +4 -0
- package/dist/routes/invalidate.d.ts.map +1 -0
- package/dist/routes/invalidate.js +114 -0
- package/dist/routes/invalidate.js.map +1 -0
- package/dist/routes/queue-events.d.ts +4 -0
- package/dist/routes/queue-events.d.ts.map +1 -0
- package/dist/routes/queue-events.js +125 -0
- package/dist/routes/queue-events.js.map +1 -0
- package/dist/routes/queue.d.ts +4 -0
- package/dist/routes/queue.d.ts.map +1 -0
- package/dist/routes/queue.js +130 -0
- package/dist/routes/queue.js.map +1 -0
- package/dist/routes/storage.d.ts +4 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +694 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/transform-helpers.d.ts +62 -0
- package/dist/routes/transform-helpers.d.ts.map +1 -0
- package/dist/routes/transform-helpers.js +372 -0
- package/dist/routes/transform-helpers.js.map +1 -0
- package/dist/routes/transform.d.ts +4 -0
- package/dist/routes/transform.d.ts.map +1 -0
- package/dist/routes/transform.js +65 -0
- package/dist/routes/transform.js.map +1 -0
- package/dist/routes/video-status.d.ts +4 -0
- package/dist/routes/video-status.d.ts.map +1 -0
- package/dist/routes/video-status.js +208 -0
- package/dist/routes/video-status.js.map +1 -0
- package/dist/services/transform.service.d.ts +71 -0
- package/dist/services/transform.service.d.ts.map +1 -0
- package/dist/services/transform.service.js +362 -0
- package/dist/services/transform.service.js.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/asset-deletion.d.ts +16 -0
- package/dist/utils/asset-deletion.d.ts.map +1 -0
- package/dist/utils/asset-deletion.js +131 -0
- package/dist/utils/asset-deletion.js.map +1 -0
- package/dist/utils/cache.d.ts +44 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +248 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/get-unique-file-path.d.ts +8 -0
- package/dist/utils/get-unique-file-path.d.ts.map +1 -0
- package/dist/utils/get-unique-file-path.js +26 -0
- package/dist/utils/get-unique-file-path.js.map +1 -0
- package/dist/utils/image/aspect-ratio.d.ts +7 -0
- package/dist/utils/image/aspect-ratio.d.ts.map +1 -0
- package/dist/utils/image/aspect-ratio.js +40 -0
- package/dist/utils/image/aspect-ratio.js.map +1 -0
- package/dist/utils/image/background.d.ts +6 -0
- package/dist/utils/image/background.d.ts.map +1 -0
- package/dist/utils/image/background.js +22 -0
- package/dist/utils/image/background.js.map +1 -0
- package/dist/utils/image/compression.d.ts +44 -0
- package/dist/utils/image/compression.d.ts.map +1 -0
- package/dist/utils/image/compression.js +345 -0
- package/dist/utils/image/compression.js.map +1 -0
- package/dist/utils/image/gravity.d.ts +6 -0
- package/dist/utils/image/gravity.d.ts.map +1 -0
- package/dist/utils/image/gravity.js +24 -0
- package/dist/utils/image/gravity.js.map +1 -0
- package/dist/utils/image/index.d.ts +8 -0
- package/dist/utils/image/index.d.ts.map +1 -0
- package/dist/utils/image/index.js +77 -0
- package/dist/utils/image/index.js.map +1 -0
- package/dist/utils/image/param-registry.d.ts +63 -0
- package/dist/utils/image/param-registry.d.ts.map +1 -0
- package/dist/utils/image/param-registry.js +196 -0
- package/dist/utils/image/param-registry.js.map +1 -0
- package/dist/utils/image/quality.d.ts +8 -0
- package/dist/utils/image/quality.d.ts.map +1 -0
- package/dist/utils/image/quality.js +9 -0
- package/dist/utils/image/quality.js.map +1 -0
- package/dist/utils/image/resize.d.ts +7 -0
- package/dist/utils/image/resize.d.ts.map +1 -0
- package/dist/utils/image/resize.js +62 -0
- package/dist/utils/image/resize.js.map +1 -0
- package/dist/utils/image/rotation.d.ts +6 -0
- package/dist/utils/image/rotation.d.ts.map +1 -0
- package/dist/utils/image/rotation.js +20 -0
- package/dist/utils/image/rotation.js.map +1 -0
- package/dist/utils/image/round-corners.d.ts +12 -0
- package/dist/utils/image/round-corners.d.ts.map +1 -0
- package/dist/utils/image/round-corners.js +102 -0
- package/dist/utils/image/round-corners.js.map +1 -0
- package/dist/utils/image/types.d.ts +2 -0
- package/dist/utils/image/types.d.ts.map +1 -0
- package/dist/utils/image/types.js +2 -0
- package/dist/utils/image/types.js.map +1 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +82 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/parser.d.ts +8 -0
- package/dist/utils/parser.d.ts.map +1 -0
- package/dist/utils/parser.js +226 -0
- package/dist/utils/parser.js.map +1 -0
- package/dist/utils/signature.d.ts +35 -0
- package/dist/utils/signature.d.ts.map +1 -0
- package/dist/utils/signature.js +100 -0
- package/dist/utils/signature.js.map +1 -0
- package/dist/utils/storage/cache.d.ts +41 -0
- package/dist/utils/storage/cache.d.ts.map +1 -0
- package/dist/utils/storage/cache.js +90 -0
- package/dist/utils/storage/cache.js.map +1 -0
- package/dist/utils/storage/cloud-storage.d.ts +188 -0
- package/dist/utils/storage/cloud-storage.d.ts.map +1 -0
- package/dist/utils/storage/cloud-storage.js +514 -0
- package/dist/utils/storage/cloud-storage.js.map +1 -0
- package/dist/utils/storage/factory.d.ts +10 -0
- package/dist/utils/storage/factory.d.ts.map +1 -0
- package/dist/utils/storage/factory.js +17 -0
- package/dist/utils/storage/factory.js.map +1 -0
- package/dist/utils/storage/index.d.ts +7 -0
- package/dist/utils/storage/index.d.ts.map +1 -0
- package/dist/utils/storage/index.js +8 -0
- package/dist/utils/storage/index.js.map +1 -0
- package/dist/utils/storage/key-generator.d.ts +11 -0
- package/dist/utils/storage/key-generator.d.ts.map +1 -0
- package/dist/utils/storage/key-generator.js +23 -0
- package/dist/utils/storage/key-generator.js.map +1 -0
- package/dist/utils/storage/listing-cache.d.ts +14 -0
- package/dist/utils/storage/listing-cache.d.ts.map +1 -0
- package/dist/utils/storage/listing-cache.js +29 -0
- package/dist/utils/storage/listing-cache.js.map +1 -0
- package/dist/utils/storage/s3-client.d.ts +97 -0
- package/dist/utils/storage/s3-client.d.ts.map +1 -0
- package/dist/utils/storage/s3-client.js +297 -0
- package/dist/utils/storage/s3-client.js.map +1 -0
- package/dist/utils/storage/stats-tracker.d.ts +45 -0
- package/dist/utils/storage/stats-tracker.d.ts.map +1 -0
- package/dist/utils/storage/stats-tracker.js +97 -0
- package/dist/utils/storage/stats-tracker.js.map +1 -0
- package/dist/utils/storage/types.d.ts +2 -0
- package/dist/utils/storage/types.d.ts.map +1 -0
- package/dist/utils/storage/types.js +2 -0
- package/dist/utils/storage/types.js.map +1 -0
- package/dist/utils/storage-level.d.ts +66 -0
- package/dist/utils/storage-level.d.ts.map +1 -0
- package/dist/utils/storage-level.js +106 -0
- package/dist/utils/storage-level.js.map +1 -0
- package/dist/utils/storage-tree.d.ts +30 -0
- package/dist/utils/storage-tree.d.ts.map +1 -0
- package/dist/utils/storage-tree.js +81 -0
- package/dist/utils/storage-tree.js.map +1 -0
- package/dist/utils/upload-signature.d.ts +25 -0
- package/dist/utils/upload-signature.d.ts.map +1 -0
- package/dist/utils/upload-signature.js +56 -0
- package/dist/utils/upload-signature.js.map +1 -0
- package/dist/utils/video/auto-downscale.d.ts +12 -0
- package/dist/utils/video/auto-downscale.d.ts.map +1 -0
- package/dist/utils/video/auto-downscale.js +29 -0
- package/dist/utils/video/auto-downscale.js.map +1 -0
- package/dist/utils/video/command-builder.d.ts +22 -0
- package/dist/utils/video/command-builder.d.ts.map +1 -0
- package/dist/utils/video/command-builder.js +79 -0
- package/dist/utils/video/command-builder.js.map +1 -0
- package/dist/utils/video/config.d.ts +51 -0
- package/dist/utils/video/config.d.ts.map +1 -0
- package/dist/utils/video/config.js +170 -0
- package/dist/utils/video/config.js.map +1 -0
- package/dist/utils/video/format.d.ts +22 -0
- package/dist/utils/video/format.d.ts.map +1 -0
- package/dist/utils/video/format.js +64 -0
- package/dist/utils/video/format.js.map +1 -0
- package/dist/utils/video/index.d.ts +25 -0
- package/dist/utils/video/index.d.ts.map +1 -0
- package/dist/utils/video/index.js +104 -0
- package/dist/utils/video/index.js.map +1 -0
- package/dist/utils/video/param-registry.d.ts +64 -0
- package/dist/utils/video/param-registry.d.ts.map +1 -0
- package/dist/utils/video/param-registry.js +201 -0
- package/dist/utils/video/param-registry.js.map +1 -0
- package/dist/utils/video/quality.d.ts +12 -0
- package/dist/utils/video/quality.d.ts.map +1 -0
- package/dist/utils/video/quality.js +45 -0
- package/dist/utils/video/quality.js.map +1 -0
- package/dist/utils/video/queue-store.d.ts +51 -0
- package/dist/utils/video/queue-store.d.ts.map +1 -0
- package/dist/utils/video/queue-store.js +2 -0
- package/dist/utils/video/queue-store.js.map +1 -0
- package/dist/utils/video/resize.d.ts +7 -0
- package/dist/utils/video/resize.d.ts.map +1 -0
- package/dist/utils/video/resize.js +40 -0
- package/dist/utils/video/resize.js.map +1 -0
- package/dist/utils/video/sqlite-video-job-store.d.ts +29 -0
- package/dist/utils/video/sqlite-video-job-store.d.ts.map +1 -0
- package/dist/utils/video/sqlite-video-job-store.js +320 -0
- package/dist/utils/video/sqlite-video-job-store.js.map +1 -0
- package/dist/utils/video/thumbnail.d.ts +7 -0
- package/dist/utils/video/thumbnail.d.ts.map +1 -0
- package/dist/utils/video/thumbnail.js +22 -0
- package/dist/utils/video/thumbnail.js.map +1 -0
- package/dist/utils/video/trim.d.ts +7 -0
- package/dist/utils/video/trim.d.ts.map +1 -0
- package/dist/utils/video/trim.js +36 -0
- package/dist/utils/video/trim.js.map +1 -0
- package/dist/utils/video/types.d.ts +19 -0
- package/dist/utils/video/types.d.ts.map +1 -0
- package/dist/utils/video/types.js +2 -0
- package/dist/utils/video/types.js.map +1 -0
- package/dist/utils/video/video-info.d.ts +25 -0
- package/dist/utils/video/video-info.d.ts.map +1 -0
- package/dist/utils/video/video-info.js +58 -0
- package/dist/utils/video/video-info.js.map +1 -0
- package/dist/utils/video/video-worker.d.ts +52 -0
- package/dist/utils/video/video-worker.d.ts.map +1 -0
- package/dist/utils/video/video-worker.js +192 -0
- package/dist/utils/video/video-worker.js.map +1 -0
- package/dist/utils/video-job-queue.d.ts +62 -0
- package/dist/utils/video-job-queue.d.ts.map +1 -0
- package/dist/utils/video-job-queue.js +122 -0
- package/dist/utils/video-job-queue.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
/**
|
|
3
|
+
* Get log level from environment variable
|
|
4
|
+
* Defaults to 'info' in production, 'debug' in development
|
|
5
|
+
*/
|
|
6
|
+
function getLogLevel() {
|
|
7
|
+
const envLevel = process.env.LOG_LEVEL?.toLowerCase();
|
|
8
|
+
const nodeEnv = process.env.NODE_ENV?.toLowerCase() || "development";
|
|
9
|
+
if (envLevel) {
|
|
10
|
+
return envLevel;
|
|
11
|
+
}
|
|
12
|
+
// Default levels by environment
|
|
13
|
+
if (nodeEnv === "production") {
|
|
14
|
+
return "info";
|
|
15
|
+
}
|
|
16
|
+
return "debug";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Serialize any error value into a plain object that survives JSON.stringify.
|
|
20
|
+
*
|
|
21
|
+
* Problem: Error.message / Error.stack / Error.name are non-enumerable, so
|
|
22
|
+
* JSON.stringify(new Error('x')) === '{}'. pino's transport worker thread
|
|
23
|
+
* uses structuredClone / JSON serialization internally, which drops those
|
|
24
|
+
* properties even when stdSerializers are configured.
|
|
25
|
+
*
|
|
26
|
+
* Solution: always call serializeError() before passing an error to pino so
|
|
27
|
+
* the resulting object only contains plain enumerable string fields.
|
|
28
|
+
*/
|
|
29
|
+
export function serializeError(error) {
|
|
30
|
+
if (error === null || error === undefined) {
|
|
31
|
+
return { message: String(error) };
|
|
32
|
+
}
|
|
33
|
+
if (error instanceof Error) {
|
|
34
|
+
const obj = {
|
|
35
|
+
type: error.constructor?.name ?? "Error",
|
|
36
|
+
message: error.message,
|
|
37
|
+
stack: error.stack,
|
|
38
|
+
};
|
|
39
|
+
// Capture any extra enumerable properties (e.g. `code` on fs errors)
|
|
40
|
+
for (const key of Object.keys(error)) {
|
|
41
|
+
if (!(key in obj)) {
|
|
42
|
+
obj[key] = error[key];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return obj;
|
|
46
|
+
}
|
|
47
|
+
if (typeof error === "object") {
|
|
48
|
+
return { ...error };
|
|
49
|
+
}
|
|
50
|
+
return { message: String(error) };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a structured logger instance
|
|
54
|
+
* - In development: pretty printed logs with colors
|
|
55
|
+
* - In production: JSON structured logs
|
|
56
|
+
*/
|
|
57
|
+
const logger = pino({
|
|
58
|
+
level: getLogLevel(),
|
|
59
|
+
// stdSerializers covers the JSON transport (production).
|
|
60
|
+
// In development (pino-pretty worker thread) we also call serializeError()
|
|
61
|
+
// explicitly at every log site to guarantee visibility.
|
|
62
|
+
serializers: {
|
|
63
|
+
error: pino.stdSerializers.err,
|
|
64
|
+
err: pino.stdSerializers.err,
|
|
65
|
+
},
|
|
66
|
+
transport: process.env.NODE_ENV === "production"
|
|
67
|
+
? undefined // Use default JSON output in production
|
|
68
|
+
: {
|
|
69
|
+
target: "pino-pretty",
|
|
70
|
+
options: {
|
|
71
|
+
colorize: true,
|
|
72
|
+
translateTime: "HH:MM:ss Z",
|
|
73
|
+
ignore: "pid,hostname",
|
|
74
|
+
singleLine: false,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
base: {
|
|
78
|
+
env: process.env.NODE_ENV || "development",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
export default logger;
|
|
82
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;;GAGG;AACH,SAAS,WAAW;IAClB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC;IACtD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,aAAa,CAAC;IAErE,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,gCAAgC;IAChC,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACpC,CAAC;IACD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,GAAG,GAA4B;YACnC,IAAI,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,IAAI,OAAO;YACxC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC;QACF,qEAAqE;QACrE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;gBAClB,GAAG,CAAC,GAAG,CAAC,GAAI,KAAa,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,GAAI,KAAgB,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE;IACpB,yDAAyD;IACzD,2EAA2E;IAC3E,wDAAwD;IACxD,WAAW,EAAE;QACX,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,GAAG;QAC9B,GAAG,EAAE,IAAI,CAAC,cAAc,CAAC,GAAG;KAC7B;IACD,SAAS,EACP,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QACnC,CAAC,CAAC,SAAS,CAAC,wCAAwC;QACpD,CAAC,CAAC;YACE,MAAM,EAAE,aAAa;YACrB,OAAO,EAAE;gBACP,QAAQ,EAAE,IAAI;gBACd,aAAa,EAAE,YAAY;gBAC3B,MAAM,EAAE,cAAc;gBACtB,UAAU,EAAE,KAAK;aAClB;SACF;IACP,IAAI,EAAE;QACJ,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa;KAC3C;CACF,CAAC,CAAC;AAEH,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const parseParams: (path: string) => Record<string, string>;
|
|
2
|
+
/**
|
|
3
|
+
* Check if a path segment is a transformation definition (e.g. "w_300,h_300,c_fill").
|
|
4
|
+
* Every comma-separated part must be a valid key_value pair to avoid false positives
|
|
5
|
+
* on folder names like "w_photos", "f_family", or "bg_images".
|
|
6
|
+
*/
|
|
7
|
+
export declare const isTransformSegment: (segment: string) => boolean;
|
|
8
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/utils/parser.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,2BAevC,CAAC;AAkCF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,OAIpD,CAAC"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
export const parseParams = (path) => {
|
|
2
|
+
const segments = path.split("/");
|
|
3
|
+
const params = {};
|
|
4
|
+
const tIndex = segments.indexOf("t");
|
|
5
|
+
if (tIndex !== -1 && segments.length > tIndex + 1) {
|
|
6
|
+
const transformSegment = segments[tIndex + 1];
|
|
7
|
+
if (isTransformSegment(transformSegment)) {
|
|
8
|
+
const transformParams = parseTransform(transformSegment);
|
|
9
|
+
Object.assign(params, transformParams);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return params;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Valid values for each transformation key.
|
|
16
|
+
* Used by isTransformSegment to reject folder names that happen to start with a known key.
|
|
17
|
+
*/
|
|
18
|
+
const TRANSFORM_VALUE_PATTERNS = {
|
|
19
|
+
w: /^\d+$|^auto$/,
|
|
20
|
+
h: /^\d+$|^auto$/,
|
|
21
|
+
c: /^(fill|lfill|fill_pad|fit|limit|mfit|scale|crop|thumb|pad|lpad)$/,
|
|
22
|
+
g: /^(center|c|north(?:_center)?|n|south(?:_center)?|s|east|e|west|w|faces?(?:_center)?|auto)$/,
|
|
23
|
+
q: /^\d+$|^auto$/,
|
|
24
|
+
f: /^(webp|jpe?g|png|avif|gif|psd|mp4|webm|mov|avi|mp3|wav|ogg|pdf|auto)$/,
|
|
25
|
+
a: /^-?\d+$|^auto$/,
|
|
26
|
+
ar: /^\d+:\d+$|^\d+(?:\.\d+)?$/,
|
|
27
|
+
b: /^(transparent|white|black|rgb:[0-9a-fA-F]{3,8}|#?[0-9a-fA-F]{3,8})$/,
|
|
28
|
+
bg: /^(transparent|white|black|rgb:[0-9a-fA-F]{3,8}|#?[0-9a-fA-F]{3,8})$/,
|
|
29
|
+
so: /^\d+(?:\.\d+)?$/,
|
|
30
|
+
eo: /^\d+(?:\.\d+)?$/,
|
|
31
|
+
t: /^(true|1|\d+)$/,
|
|
32
|
+
tt: /^\d+(?:\.\d+)?$/,
|
|
33
|
+
r: /^max$|^\d+(?::\d+){0,3}$/,
|
|
34
|
+
};
|
|
35
|
+
const isValidTransformPair = (part) => {
|
|
36
|
+
const underscoreIndex = part.indexOf("_");
|
|
37
|
+
if (underscoreIndex === -1)
|
|
38
|
+
return false;
|
|
39
|
+
const key = part.substring(0, underscoreIndex);
|
|
40
|
+
const value = part.substring(underscoreIndex + 1);
|
|
41
|
+
if (!value)
|
|
42
|
+
return false;
|
|
43
|
+
const pattern = TRANSFORM_VALUE_PATTERNS[key];
|
|
44
|
+
return pattern !== undefined && pattern.test(value);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Check if a path segment is a transformation definition (e.g. "w_300,h_300,c_fill").
|
|
48
|
+
* Every comma-separated part must be a valid key_value pair to avoid false positives
|
|
49
|
+
* on folder names like "w_photos", "f_family", or "bg_images".
|
|
50
|
+
*/
|
|
51
|
+
export const isTransformSegment = (segment) => {
|
|
52
|
+
if (!segment || segment.includes("."))
|
|
53
|
+
return false;
|
|
54
|
+
const parts = segment.split(",").filter(Boolean);
|
|
55
|
+
return parts.length > 0 && parts.every(isValidTransformPair);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Parse a single transformation segment into our
|
|
59
|
+
* internal parameter map.
|
|
60
|
+
*/
|
|
61
|
+
const parseTransform = (segment) => {
|
|
62
|
+
const params = {};
|
|
63
|
+
const parts = segment.split(",");
|
|
64
|
+
let width;
|
|
65
|
+
let height;
|
|
66
|
+
let cropMode;
|
|
67
|
+
let startOffset;
|
|
68
|
+
let endOffset;
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
if (!part)
|
|
71
|
+
continue;
|
|
72
|
+
const underscoreIndex = part.indexOf("_");
|
|
73
|
+
if (underscoreIndex === -1)
|
|
74
|
+
continue;
|
|
75
|
+
const key = part.substring(0, underscoreIndex);
|
|
76
|
+
const value = part.substring(underscoreIndex + 1);
|
|
77
|
+
switch (key) {
|
|
78
|
+
case "w":
|
|
79
|
+
width = value;
|
|
80
|
+
break;
|
|
81
|
+
case "h":
|
|
82
|
+
height = value;
|
|
83
|
+
break;
|
|
84
|
+
case "c":
|
|
85
|
+
cropMode = mapCropMode(value);
|
|
86
|
+
break;
|
|
87
|
+
case "g":
|
|
88
|
+
params.gravity = mapGravity(value);
|
|
89
|
+
break;
|
|
90
|
+
case "q":
|
|
91
|
+
// Quality (e.g. q_80, q_auto)
|
|
92
|
+
params.quality = value;
|
|
93
|
+
break;
|
|
94
|
+
case "f":
|
|
95
|
+
// Map directly, validation happens downstream
|
|
96
|
+
params.format = value;
|
|
97
|
+
break;
|
|
98
|
+
case "a":
|
|
99
|
+
// Angle of rotation (e.g. a_90, a_auto)
|
|
100
|
+
params.rotate = value;
|
|
101
|
+
break;
|
|
102
|
+
case "ar":
|
|
103
|
+
// Aspect ratio, usually like "16:9" already
|
|
104
|
+
params.aspect = value;
|
|
105
|
+
break;
|
|
106
|
+
case "b":
|
|
107
|
+
case "bg":
|
|
108
|
+
params.background = mapBackground(value);
|
|
109
|
+
break;
|
|
110
|
+
case "so":
|
|
111
|
+
// Start offset (in seconds) for video/audio
|
|
112
|
+
startOffset = value;
|
|
113
|
+
break;
|
|
114
|
+
case "eo":
|
|
115
|
+
// End offset (in seconds) for video/audio
|
|
116
|
+
endOffset = value;
|
|
117
|
+
break;
|
|
118
|
+
case "t":
|
|
119
|
+
// FIX H12: Parse thumbnail parameter
|
|
120
|
+
params.thumbnail = value;
|
|
121
|
+
break;
|
|
122
|
+
case "tt":
|
|
123
|
+
// FIX H12: Parse thumbnail time parameter
|
|
124
|
+
params.thumbnailTime = value;
|
|
125
|
+
break;
|
|
126
|
+
case "r":
|
|
127
|
+
params.radius = value;
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
// Ignore unsupported/unknown directives for now
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Set width and/or height independently
|
|
135
|
+
if (width) {
|
|
136
|
+
params.width = width;
|
|
137
|
+
}
|
|
138
|
+
if (height) {
|
|
139
|
+
params.height = height;
|
|
140
|
+
}
|
|
141
|
+
// If both are present, also set resize for backwards compatibility
|
|
142
|
+
if (width && height) {
|
|
143
|
+
params.resize = `${width}x${height}`;
|
|
144
|
+
}
|
|
145
|
+
if (cropMode) {
|
|
146
|
+
params.crop = cropMode;
|
|
147
|
+
}
|
|
148
|
+
if (startOffset) {
|
|
149
|
+
params.startOffset = startOffset;
|
|
150
|
+
}
|
|
151
|
+
if (endOffset) {
|
|
152
|
+
params.endOffset = endOffset;
|
|
153
|
+
}
|
|
154
|
+
return params;
|
|
155
|
+
};
|
|
156
|
+
const mapCropMode = (value) => {
|
|
157
|
+
switch (value) {
|
|
158
|
+
case "fill":
|
|
159
|
+
case "lfill":
|
|
160
|
+
case "fill_pad":
|
|
161
|
+
return "fill";
|
|
162
|
+
case "fit":
|
|
163
|
+
case "limit":
|
|
164
|
+
case "mfit":
|
|
165
|
+
return "fit";
|
|
166
|
+
case "scale":
|
|
167
|
+
return "scale";
|
|
168
|
+
case "crop":
|
|
169
|
+
case "thumb":
|
|
170
|
+
return "crop";
|
|
171
|
+
case "pad":
|
|
172
|
+
case "lpad":
|
|
173
|
+
return "pad";
|
|
174
|
+
default:
|
|
175
|
+
return "fill";
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const mapGravity = (value) => {
|
|
179
|
+
switch (value) {
|
|
180
|
+
case "center":
|
|
181
|
+
case "c":
|
|
182
|
+
return "center";
|
|
183
|
+
case "north":
|
|
184
|
+
case "north_center":
|
|
185
|
+
case "n":
|
|
186
|
+
return "north";
|
|
187
|
+
case "south":
|
|
188
|
+
case "south_center":
|
|
189
|
+
case "s":
|
|
190
|
+
return "south";
|
|
191
|
+
case "east":
|
|
192
|
+
case "e":
|
|
193
|
+
return "east";
|
|
194
|
+
case "west":
|
|
195
|
+
case "w":
|
|
196
|
+
return "west";
|
|
197
|
+
case "face":
|
|
198
|
+
case "faces":
|
|
199
|
+
case "face_center":
|
|
200
|
+
return "face";
|
|
201
|
+
case "auto":
|
|
202
|
+
return "auto";
|
|
203
|
+
default:
|
|
204
|
+
return "center";
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const mapBackground = (value) => {
|
|
208
|
+
if (value.startsWith("rgb:")) {
|
|
209
|
+
const hex = value.substring("rgb:".length);
|
|
210
|
+
return `#${hex}`;
|
|
211
|
+
}
|
|
212
|
+
switch (value) {
|
|
213
|
+
case "transparent":
|
|
214
|
+
return "transparent";
|
|
215
|
+
case "white":
|
|
216
|
+
return "#ffffff";
|
|
217
|
+
case "black":
|
|
218
|
+
return "#000000";
|
|
219
|
+
default:
|
|
220
|
+
// Assume it's already a hex-like string
|
|
221
|
+
if (value.startsWith("#"))
|
|
222
|
+
return value;
|
|
223
|
+
return `#${value}`;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/utils/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature utility for authenticated delivery URLs
|
|
3
|
+
* Uses HMAC-SHA256 for cryptographically secure signatures
|
|
4
|
+
*/
|
|
5
|
+
export declare const SIGNATURE_LENGTH = 16;
|
|
6
|
+
/**
|
|
7
|
+
* Sanitizes a file path to prevent path traversal attacks
|
|
8
|
+
* @param filePath - The file path to sanitize
|
|
9
|
+
* @returns Sanitized file path
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeFilePath(filePath: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generates a cryptographically secure signature for authenticated URLs
|
|
14
|
+
* @param transformations - The transformation string (e.g., "c_fill,w_300,h_250")
|
|
15
|
+
* @param filePath - The file path
|
|
16
|
+
* @param secret - The API secret key
|
|
17
|
+
* @returns The signature (first SIGNATURE_LENGTH characters of HMAC-SHA256)
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateSignature(transformations: string, filePath: string, secret: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Verifies a signature using timing-safe comparison
|
|
22
|
+
* @param providedSignature - The signature provided in the URL
|
|
23
|
+
* @param transformations - The transformation string
|
|
24
|
+
* @param filePath - The file path
|
|
25
|
+
* @param secret - The API secret key
|
|
26
|
+
* @returns True if the signature is valid, false otherwise
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifySignature(providedSignature: string, transformations: string, filePath: string, secret: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Validates that the API_SECRET is properly configured
|
|
31
|
+
* @param secret - The API secret to validate
|
|
32
|
+
* @throws Error if the secret is not configured or is too weak
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateApiSecret(secret: string | undefined): void;
|
|
35
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/utils/signature.ts"],"names":[],"mappings":"AAIA;;;GAGG;AAEH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAazD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,MAAM,CAmBR;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,iBAAiB,EAAE,MAAM,EACzB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAiBlE"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import logger, { serializeError } from './logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Signature utility for authenticated delivery URLs
|
|
6
|
+
* Uses HMAC-SHA256 for cryptographically secure signatures
|
|
7
|
+
*/
|
|
8
|
+
export const SIGNATURE_LENGTH = 16; // 64 bits of entropy
|
|
9
|
+
/**
|
|
10
|
+
* Sanitizes a file path to prevent path traversal attacks
|
|
11
|
+
* @param filePath - The file path to sanitize
|
|
12
|
+
* @returns Sanitized file path
|
|
13
|
+
*/
|
|
14
|
+
export function sanitizeFilePath(filePath) {
|
|
15
|
+
// Normalize the path to resolve any '..' or '.' segments
|
|
16
|
+
const normalized = path.normalize(filePath);
|
|
17
|
+
// Remove any leading path traversal sequences
|
|
18
|
+
const sanitized = normalized.replace(/^(\.\.[/\\])+/, '');
|
|
19
|
+
// Additional check: reject paths that still contain '..'
|
|
20
|
+
if (sanitized.includes('..')) {
|
|
21
|
+
throw new Error('Invalid file path: path traversal detected');
|
|
22
|
+
}
|
|
23
|
+
return sanitized;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generates a cryptographically secure signature for authenticated URLs
|
|
27
|
+
* @param transformations - The transformation string (e.g., "c_fill,w_300,h_250")
|
|
28
|
+
* @param filePath - The file path
|
|
29
|
+
* @param secret - The API secret key
|
|
30
|
+
* @returns The signature (first SIGNATURE_LENGTH characters of HMAC-SHA256)
|
|
31
|
+
*/
|
|
32
|
+
export function generateSignature(transformations, filePath, secret) {
|
|
33
|
+
if (!secret) {
|
|
34
|
+
throw new Error('API_SECRET is required for signature generation');
|
|
35
|
+
}
|
|
36
|
+
// Sanitize the file path to prevent path traversal
|
|
37
|
+
const sanitizedPath = sanitizeFilePath(filePath);
|
|
38
|
+
// Create the string to sign
|
|
39
|
+
const stringToSign = transformations
|
|
40
|
+
? `${transformations}/${sanitizedPath}`
|
|
41
|
+
: sanitizedPath;
|
|
42
|
+
// Generate HMAC-SHA256 signature
|
|
43
|
+
const hmac = crypto.createHmac('sha256', secret);
|
|
44
|
+
hmac.update(stringToSign);
|
|
45
|
+
const signature = hmac.digest('hex').substring(0, SIGNATURE_LENGTH);
|
|
46
|
+
return signature;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Verifies a signature using timing-safe comparison
|
|
50
|
+
* @param providedSignature - The signature provided in the URL
|
|
51
|
+
* @param transformations - The transformation string
|
|
52
|
+
* @param filePath - The file path
|
|
53
|
+
* @param secret - The API secret key
|
|
54
|
+
* @returns True if the signature is valid, false otherwise
|
|
55
|
+
*/
|
|
56
|
+
export function verifySignature(providedSignature, transformations, filePath, secret) {
|
|
57
|
+
if (!secret) {
|
|
58
|
+
logger.error('API_SECRET is not configured for signature verification');
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// Validate signature length
|
|
62
|
+
if (providedSignature.length !== SIGNATURE_LENGTH) {
|
|
63
|
+
logger.warn({ providedLength: providedSignature.length, expectedLength: SIGNATURE_LENGTH }, 'Invalid signature length');
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
// Generate the expected signature
|
|
68
|
+
const expectedSignature = generateSignature(transformations, filePath, secret);
|
|
69
|
+
// Use timing-safe comparison to prevent timing attacks
|
|
70
|
+
const providedBuffer = Buffer.from(providedSignature, 'utf8');
|
|
71
|
+
const expectedBuffer = Buffer.from(expectedSignature, 'utf8');
|
|
72
|
+
// Both buffers must have the same length for timingSafeEqual
|
|
73
|
+
if (providedBuffer.length !== expectedBuffer.length) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return crypto.timingSafeEqual(providedBuffer, expectedBuffer);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
logger.error({ error: serializeError(error) }, 'Error during signature verification');
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validates that the API_SECRET is properly configured
|
|
85
|
+
* @param secret - The API secret to validate
|
|
86
|
+
* @throws Error if the secret is not configured or is too weak
|
|
87
|
+
*/
|
|
88
|
+
export function validateApiSecret(secret) {
|
|
89
|
+
if (!secret) {
|
|
90
|
+
throw new Error('API_SECRET environment variable is required for authenticated delivery URLs. ' +
|
|
91
|
+
'Please set a strong secret key in your environment configuration.');
|
|
92
|
+
}
|
|
93
|
+
// Ensure the secret is reasonably strong (at least 16 characters)
|
|
94
|
+
if (secret.length < 16) {
|
|
95
|
+
throw new Error('API_SECRET must be at least 16 characters long for adequate security. ' +
|
|
96
|
+
`Current length: ${secret.length}`);
|
|
97
|
+
}
|
|
98
|
+
logger.info('API_SECRET validation passed');
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/utils/signature.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CacheEntry } from './types';
|
|
2
|
+
export declare class StorageCache {
|
|
3
|
+
private existsCache;
|
|
4
|
+
private readonly CACHE_TTL;
|
|
5
|
+
private readonly NEGATIVE_CACHE_TTL;
|
|
6
|
+
private readonly MAX_CACHE_SIZE;
|
|
7
|
+
constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Gets a cache entry
|
|
10
|
+
*/
|
|
11
|
+
get(key: string): CacheEntry | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Sets a cache entry with size management
|
|
14
|
+
*/
|
|
15
|
+
set(key: string, entry: CacheEntry): void;
|
|
16
|
+
/**
|
|
17
|
+
* Deletes a cache entry
|
|
18
|
+
*/
|
|
19
|
+
delete(key: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Checks if cache hit is valid
|
|
22
|
+
*/
|
|
23
|
+
isValidHit(key: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Gets cache size
|
|
26
|
+
*/
|
|
27
|
+
get size(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Gets all cache keys
|
|
30
|
+
*/
|
|
31
|
+
getAllKeys(): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Clears all cache entries
|
|
34
|
+
*/
|
|
35
|
+
clear(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Automatic cache cleanup (LRU + TTL)
|
|
38
|
+
*/
|
|
39
|
+
private cleanup;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/utils/storage/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAa;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;;IAOxC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAkBxC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IASzC;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAKhC;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAItB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,CAAC,OAAO;CAuBhB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export class StorageCache {
|
|
2
|
+
existsCache = new Map();
|
|
3
|
+
CACHE_TTL = 60 * 1000; // 1 minute (reduced from 5 minutes to detect deletions faster)
|
|
4
|
+
NEGATIVE_CACHE_TTL = 30 * 1000; // 30 seconds for "not found"
|
|
5
|
+
MAX_CACHE_SIZE = 10000; // Maximum 10k entries
|
|
6
|
+
constructor() {
|
|
7
|
+
// Automatic cache cleanup every 10 minutes
|
|
8
|
+
setInterval(() => this.cleanup(), 10 * 60 * 1000);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Gets a cache entry
|
|
12
|
+
*/
|
|
13
|
+
get(key) {
|
|
14
|
+
const cached = this.existsCache.get(key);
|
|
15
|
+
if (cached) {
|
|
16
|
+
const age = Date.now() - cached.timestamp;
|
|
17
|
+
const ttl = cached.exists ? this.CACHE_TTL : this.NEGATIVE_CACHE_TTL;
|
|
18
|
+
if (age < ttl) {
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
// Remove expired entry
|
|
22
|
+
this.existsCache.delete(key);
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sets a cache entry with size management
|
|
28
|
+
*/
|
|
29
|
+
set(key, entry) {
|
|
30
|
+
// Cleanup if cache becomes too large
|
|
31
|
+
if (this.existsCache.size >= this.MAX_CACHE_SIZE) {
|
|
32
|
+
this.cleanup();
|
|
33
|
+
}
|
|
34
|
+
this.existsCache.set(key, entry);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Deletes a cache entry
|
|
38
|
+
*/
|
|
39
|
+
delete(key) {
|
|
40
|
+
this.existsCache.delete(key);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Checks if cache hit is valid
|
|
44
|
+
*/
|
|
45
|
+
isValidHit(key) {
|
|
46
|
+
const cached = this.get(key);
|
|
47
|
+
return cached !== undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Gets cache size
|
|
51
|
+
*/
|
|
52
|
+
get size() {
|
|
53
|
+
return this.existsCache.size;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gets all cache keys
|
|
57
|
+
*/
|
|
58
|
+
getAllKeys() {
|
|
59
|
+
return Array.from(this.existsCache.keys());
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Clears all cache entries
|
|
63
|
+
*/
|
|
64
|
+
clear() {
|
|
65
|
+
this.existsCache.clear();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Automatic cache cleanup (LRU + TTL)
|
|
69
|
+
*/
|
|
70
|
+
cleanup() {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
for (const [key, value] of this.existsCache.entries()) {
|
|
73
|
+
const age = now - value.timestamp;
|
|
74
|
+
const ttl = value.exists ? this.CACHE_TTL : this.NEGATIVE_CACHE_TTL;
|
|
75
|
+
if (age > ttl) {
|
|
76
|
+
this.existsCache.delete(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// If still too large, remove the oldest entries
|
|
80
|
+
if (this.existsCache.size > this.MAX_CACHE_SIZE * 0.8) {
|
|
81
|
+
const entries = Array.from(this.existsCache.entries())
|
|
82
|
+
.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
83
|
+
const toRemove = entries.slice(0, Math.floor(entries.length * 0.2));
|
|
84
|
+
toRemove.forEach(([key]) => {
|
|
85
|
+
this.existsCache.delete(key);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/utils/storage/cache.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,YAAY;IACf,WAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;IACnC,SAAS,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,+DAA+D;IACtF,kBAAkB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,6BAA6B;IAC7D,cAAc,GAAG,KAAK,CAAC,CAAC,sBAAsB;IAE/D;QACE,2CAA2C;QAC3C,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,GAAW;QACb,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEzC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;YAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC;YAErE,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;gBACd,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,uBAAuB;YACvB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,GAAW,EAAE,KAAiB;QAChC,qCAAqC;QACrC,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACjD,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,GAAW;QAChB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,GAAW;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,OAAO,MAAM,KAAK,SAAS,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IACK,OAAO;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,MAAM,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC;YAClC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC;YAEpE,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;gBACd,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,gDAAgD;QAChD,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,IAAI,CAAC,cAAc,GAAG,GAAG,EAAE,CAAC;YACtD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;iBACnD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAEnD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC;YACpE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE;gBACzB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/B,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
|