@mr-aftab-ahmad-khan/imago 0.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/dist/index.cjs ADDED
@@ -0,0 +1,436 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ DimensionLimitError: () => DimensionLimitError,
34
+ DiskCache: () => DiskCache,
35
+ FileSystemSource: () => FileSystemSource,
36
+ ImagoError: () => ImagoError,
37
+ LruCache: () => LruCache,
38
+ SourceNotFoundError: () => SourceNotFoundError,
39
+ TransformOptionsSchema: () => TransformOptionsSchema,
40
+ UnsupportedFormatError: () => UnsupportedFormatError,
41
+ handle: () => handle,
42
+ imago: () => imago,
43
+ imagoFastify: () => imagoFastify,
44
+ imagoHono: () => imagoHono,
45
+ makeCacheKey: () => makeCacheKey,
46
+ pickFormat: () => pickFormat,
47
+ transform: () => transform
48
+ });
49
+ module.exports = __toCommonJS(src_exports);
50
+
51
+ // src/middleware.ts
52
+ var import_node_fs3 = require("fs");
53
+
54
+ // src/types.ts
55
+ var import_zod = require("zod");
56
+ var FORMATS = ["jpeg", "png", "webp", "avif", "auto"];
57
+ var FITS = ["cover", "contain", "fill", "inside", "outside"];
58
+ var GRAVITIES = ["center", "north", "south", "east", "west", "smart"];
59
+ var TransformOptionsSchema = import_zod.z.object({
60
+ w: import_zod.z.coerce.number().int().min(1).max(4e3).optional(),
61
+ h: import_zod.z.coerce.number().int().min(1).max(4e3).optional(),
62
+ fit: import_zod.z.enum(FITS).default("cover"),
63
+ format: import_zod.z.enum(FORMATS).default("auto"),
64
+ q: import_zod.z.coerce.number().int().min(1).max(100).optional(),
65
+ blur: import_zod.z.coerce.number().min(0.3).max(1e3).optional(),
66
+ gravity: import_zod.z.enum(GRAVITIES).default("center"),
67
+ strip: import_zod.z.coerce.boolean().default(false),
68
+ sharpen: import_zod.z.coerce.boolean().default(false),
69
+ placeholder: import_zod.z.enum(["blur"]).optional()
70
+ });
71
+
72
+ // src/source.ts
73
+ var import_node_fs = require("fs");
74
+ var import_node_path = require("path");
75
+
76
+ // src/errors.ts
77
+ var ImagoError = class _ImagoError extends Error {
78
+ status;
79
+ constructor(message, status) {
80
+ super(message);
81
+ this.name = "ImagoError";
82
+ this.status = status;
83
+ Object.setPrototypeOf(this, _ImagoError.prototype);
84
+ }
85
+ };
86
+ var DimensionLimitError = class _DimensionLimitError extends ImagoError {
87
+ constructor(requested, max) {
88
+ super(`Requested dimension ${requested} exceeds maximum ${max}`, 413);
89
+ this.requested = requested;
90
+ this.max = max;
91
+ this.name = "DimensionLimitError";
92
+ Object.setPrototypeOf(this, _DimensionLimitError.prototype);
93
+ }
94
+ requested;
95
+ max;
96
+ };
97
+ var UnsupportedFormatError = class _UnsupportedFormatError extends ImagoError {
98
+ constructor(format) {
99
+ super(`Unsupported image format: ${format}`, 400);
100
+ this.format = format;
101
+ this.name = "UnsupportedFormatError";
102
+ Object.setPrototypeOf(this, _UnsupportedFormatError.prototype);
103
+ }
104
+ format;
105
+ };
106
+ var SourceNotFoundError = class _SourceNotFoundError extends ImagoError {
107
+ constructor(key) {
108
+ super(`Source image not found: ${key}`, 404);
109
+ this.key = key;
110
+ this.name = "SourceNotFoundError";
111
+ Object.setPrototypeOf(this, _SourceNotFoundError.prototype);
112
+ }
113
+ key;
114
+ };
115
+
116
+ // src/source.ts
117
+ var FileSystemSource = class {
118
+ root;
119
+ constructor(root) {
120
+ this.root = (0, import_node_path.resolve)(root);
121
+ }
122
+ async fetch(key) {
123
+ const abs = (0, import_node_path.resolve)(this.root, key);
124
+ if (!abs.startsWith(this.root + import_node_path.sep) && abs !== this.root) {
125
+ throw new SourceNotFoundError(key);
126
+ }
127
+ if (!(0, import_node_fs.existsSync)(abs)) return void 0;
128
+ const buffer = (0, import_node_fs.readFileSync)(abs);
129
+ return { buffer, mimeType: mimeFromExtension(abs) };
130
+ }
131
+ };
132
+ function mimeFromExtension(path) {
133
+ const ext = path.toLowerCase().slice(path.lastIndexOf(".") + 1);
134
+ switch (ext) {
135
+ case "jpg":
136
+ case "jpeg":
137
+ return "image/jpeg";
138
+ case "png":
139
+ return "image/png";
140
+ case "webp":
141
+ return "image/webp";
142
+ case "avif":
143
+ return "image/avif";
144
+ case "gif":
145
+ return "image/gif";
146
+ case "bmp":
147
+ return "image/bmp";
148
+ default:
149
+ return "application/octet-stream";
150
+ }
151
+ }
152
+
153
+ // src/transform.ts
154
+ var import_node_crypto = require("crypto");
155
+ var _sharp;
156
+ async function loadSharp() {
157
+ if (!_sharp) {
158
+ try {
159
+ const mod = await import(
160
+ /* @vite-ignore */
161
+ "sharp"
162
+ );
163
+ _sharp = mod.default;
164
+ } catch (err) {
165
+ throw new Error(
166
+ "imago requires `sharp >= 0.33.0` as a peer dependency. Install it with `npm install sharp`."
167
+ );
168
+ }
169
+ }
170
+ return _sharp;
171
+ }
172
+ var QUALITY_DEFAULTS = {
173
+ jpeg: 82,
174
+ webp: 80,
175
+ avif: 65,
176
+ png: 90
177
+ };
178
+ function pickFormat(requested, accept, sourceFormat) {
179
+ if (requested !== "auto") return requested;
180
+ const a = (accept ?? "").toLowerCase();
181
+ if (a.includes("image/avif")) return "avif";
182
+ if (a.includes("image/webp")) return "webp";
183
+ if (sourceFormat === "png") return "png";
184
+ return "jpeg";
185
+ }
186
+ async function transform(input) {
187
+ const opts = TransformOptionsSchema.parse(input.options);
188
+ const maxDim = input.maxDimension ?? 4e3;
189
+ if (opts.w && opts.w > maxDim) throw new DimensionLimitError(opts.w, maxDim);
190
+ if (opts.h && opts.h > maxDim) throw new DimensionLimitError(opts.h, maxDim);
191
+ const sharp = await loadSharp();
192
+ let pipeline = sharp(input.source).rotate();
193
+ const meta = await pipeline.metadata();
194
+ const finalFormat = pickFormat(opts.format, input.accept, meta.format);
195
+ if (!["jpeg", "png", "webp", "avif"].includes(finalFormat)) {
196
+ throw new UnsupportedFormatError(finalFormat);
197
+ }
198
+ if (opts.placeholder === "blur") {
199
+ const buf = await sharp(input.source).resize({ width: 8, height: 8, fit: "cover" }).blur(2).toFormat("webp", { quality: 50 }).toBuffer();
200
+ return {
201
+ buffer: buf,
202
+ mimeType: "image/webp",
203
+ etag: hashEtag(buf)
204
+ };
205
+ }
206
+ if (opts.w || opts.h) {
207
+ pipeline = pipeline.resize({
208
+ ...opts.w !== void 0 ? { width: opts.w } : {},
209
+ ...opts.h !== void 0 ? { height: opts.h } : {},
210
+ fit: opts.fit,
211
+ ...opts.gravity === "smart" ? { position: "attention" } : { position: opts.gravity }
212
+ });
213
+ }
214
+ if (opts.blur !== void 0) pipeline = pipeline.blur(opts.blur);
215
+ if (opts.sharpen) pipeline = pipeline.sharpen();
216
+ if (!opts.strip) pipeline = pipeline.withMetadata();
217
+ const quality = opts.q ?? QUALITY_DEFAULTS[finalFormat] ?? 80;
218
+ pipeline = pipeline.toFormat(finalFormat, { quality });
219
+ const buffer = await pipeline.toBuffer();
220
+ return {
221
+ buffer,
222
+ mimeType: `image/${finalFormat}`,
223
+ etag: hashEtag(buffer)
224
+ };
225
+ }
226
+ function hashEtag(buf) {
227
+ return `"${(0, import_node_crypto.createHash)("md5").update(buf).digest("hex")}"`;
228
+ }
229
+
230
+ // src/cache.ts
231
+ var import_node_crypto2 = require("crypto");
232
+ var import_node_fs2 = require("fs");
233
+ var import_node_path2 = require("path");
234
+ function makeCacheKey(sourceKey, opts, mtime) {
235
+ const json = JSON.stringify({ sourceKey, opts, mtime });
236
+ return (0, import_node_crypto2.createHash)("sha1").update(json).digest("hex");
237
+ }
238
+ var LruCache = class {
239
+ constructor(maxItems = 256) {
240
+ this.maxItems = maxItems;
241
+ }
242
+ maxItems;
243
+ map = /* @__PURE__ */ new Map();
244
+ get(key) {
245
+ const v = this.map.get(key);
246
+ if (!v) return void 0;
247
+ this.map.delete(key);
248
+ this.map.set(key, v);
249
+ return v;
250
+ }
251
+ set(key, entry) {
252
+ if (this.map.has(key)) this.map.delete(key);
253
+ this.map.set(key, entry);
254
+ while (this.map.size > this.maxItems) {
255
+ const firstKey = this.map.keys().next().value;
256
+ if (firstKey === void 0) break;
257
+ this.map.delete(firstKey);
258
+ }
259
+ }
260
+ };
261
+ var DiskCache = class {
262
+ constructor(dir) {
263
+ this.dir = dir;
264
+ if (!(0, import_node_fs2.existsSync)(dir)) (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
265
+ }
266
+ dir;
267
+ file(key) {
268
+ return (0, import_node_path2.join)(this.dir, key);
269
+ }
270
+ meta(key) {
271
+ return (0, import_node_path2.join)(this.dir, `${key}.json`);
272
+ }
273
+ get(key) {
274
+ if (!(0, import_node_fs2.existsSync)(this.file(key))) return void 0;
275
+ try {
276
+ const meta = JSON.parse((0, import_node_fs2.readFileSync)(this.meta(key), "utf8"));
277
+ const stat = (0, import_node_fs2.statSync)(this.file(key));
278
+ return {
279
+ buffer: (0, import_node_fs2.readFileSync)(this.file(key)),
280
+ mimeType: meta.mimeType,
281
+ etag: meta.etag,
282
+ lastModified: stat.mtime
283
+ };
284
+ } catch {
285
+ return void 0;
286
+ }
287
+ }
288
+ set(key, entry) {
289
+ (0, import_node_fs2.writeFileSync)(this.file(key), entry.buffer);
290
+ (0, import_node_fs2.writeFileSync)(this.meta(key), JSON.stringify({ mimeType: entry.mimeType, etag: entry.etag }), "utf8");
291
+ }
292
+ };
293
+
294
+ // src/middleware.ts
295
+ function resolveSource(opts) {
296
+ if (typeof opts.source === "string") return new FileSystemSource(opts.source);
297
+ return opts.source;
298
+ }
299
+ function resolveCache(opts) {
300
+ if (!opts.cache) return new LruCache();
301
+ if (typeof opts.cache === "string") return new DiskCache(opts.cache);
302
+ if (opts.cache.dir) return new DiskCache(opts.cache.dir);
303
+ return new LruCache(opts.cache.maxItems ?? 256);
304
+ }
305
+ async function handle(opts, key, rawQuery, reqHeaders) {
306
+ const source = resolveSource(opts);
307
+ const cache = resolveCache(opts);
308
+ const maxAge = opts.maxAge ?? 86400;
309
+ const parsed = TransformOptionsSchema.safeParse(rawQuery);
310
+ if (!parsed.success) {
311
+ return jsonError(400, parsed.error.message);
312
+ }
313
+ const tOpts = parsed.data;
314
+ const fetched = await source.fetch(key);
315
+ if (!fetched) {
316
+ return jsonError(404, "Source not found", key);
317
+ }
318
+ let mtime = 0;
319
+ if (typeof opts.source === "string") {
320
+ try {
321
+ mtime = (0, import_node_fs3.statSync)(`${opts.source}/${key}`).mtimeMs;
322
+ } catch {
323
+ }
324
+ }
325
+ const cacheKey = makeCacheKey(key, tOpts, mtime);
326
+ const cached = cache.get(cacheKey);
327
+ const accept = reqHeaders["accept"];
328
+ let result;
329
+ if (cached) {
330
+ result = cached;
331
+ } else {
332
+ try {
333
+ const transformed = await transform({
334
+ source: fetched.buffer,
335
+ options: tOpts,
336
+ ...accept !== void 0 ? { accept } : {},
337
+ ...opts.maxDimension !== void 0 ? { maxDimension: opts.maxDimension } : {}
338
+ });
339
+ result = { ...transformed, lastModified: /* @__PURE__ */ new Date() };
340
+ cache.set(cacheKey, result);
341
+ } catch (err) {
342
+ if (err instanceof ImagoError) return jsonError(err.status, err.message);
343
+ throw err;
344
+ }
345
+ }
346
+ const ifNoneMatch = reqHeaders["if-none-match"];
347
+ if (ifNoneMatch && ifNoneMatch === result.etag) {
348
+ return { status: 304, headers: { ETag: result.etag }, body: Buffer.alloc(0) };
349
+ }
350
+ return {
351
+ status: 200,
352
+ headers: {
353
+ "content-type": result.mimeType,
354
+ "cache-control": `public, max-age=${maxAge}, immutable`,
355
+ etag: result.etag,
356
+ "last-modified": result.lastModified.toUTCString(),
357
+ "content-length": String(result.buffer.length)
358
+ },
359
+ body: result.buffer
360
+ };
361
+ }
362
+ function jsonError(status, message, extra) {
363
+ const body = Buffer.from(JSON.stringify({ error: message, key: extra }));
364
+ return {
365
+ status,
366
+ headers: { "content-type": "application/json", "content-length": String(body.length) },
367
+ body
368
+ };
369
+ }
370
+ function imago(opts) {
371
+ return async (req, res, next) => {
372
+ try {
373
+ const url = new URL(req.url ?? "/", "http://localhost");
374
+ const key = decodeURIComponent(url.pathname.replace(/^\//, ""));
375
+ const query = {};
376
+ url.searchParams.forEach((v, k) => {
377
+ query[k] = v;
378
+ });
379
+ const result = await handle(opts, key, query, req.headers ?? {});
380
+ res.status(result.status);
381
+ for (const [k, v] of Object.entries(result.headers)) res.setHeader(k, v);
382
+ res.end(result.body);
383
+ } catch (err) {
384
+ next(err);
385
+ }
386
+ };
387
+ }
388
+ function imagoHono(opts) {
389
+ return async (c) => {
390
+ const url = new URL(c.req.url);
391
+ const key = decodeURIComponent(url.pathname.replace(/^\//, ""));
392
+ const query = {};
393
+ url.searchParams.forEach((v, k) => {
394
+ query[k] = v;
395
+ });
396
+ const reqHeaders = {};
397
+ c.req.headers.forEach((v, k) => {
398
+ reqHeaders[k.toLowerCase()] = v;
399
+ });
400
+ const result = await handle(opts, key, query, reqHeaders);
401
+ return new Response(result.body, { status: result.status, headers: result.headers });
402
+ };
403
+ }
404
+ function imagoFastify(opts) {
405
+ return async (req, reply) => {
406
+ const url = new URL(req.url, "http://localhost");
407
+ const key = decodeURIComponent(url.pathname.replace(/^\//, ""));
408
+ const query = {};
409
+ url.searchParams.forEach((v, k) => {
410
+ query[k] = v;
411
+ });
412
+ const result = await handle(opts, key, query, req.headers ?? {});
413
+ reply.code(result.status);
414
+ for (const [k, v] of Object.entries(result.headers)) reply.header(k, v);
415
+ reply.send(result.body);
416
+ };
417
+ }
418
+ // Annotate the CommonJS export names for ESM import in node:
419
+ 0 && (module.exports = {
420
+ DimensionLimitError,
421
+ DiskCache,
422
+ FileSystemSource,
423
+ ImagoError,
424
+ LruCache,
425
+ SourceNotFoundError,
426
+ TransformOptionsSchema,
427
+ UnsupportedFormatError,
428
+ handle,
429
+ imago,
430
+ imagoFastify,
431
+ imagoHono,
432
+ makeCacheKey,
433
+ pickFormat,
434
+ transform
435
+ });
436
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/types.ts","../src/source.ts","../src/errors.ts","../src/transform.ts","../src/cache.ts"],"sourcesContent":["export { imago, imagoHono, imagoFastify, handle } from \"./middleware.js\";\nexport { transform, pickFormat } from \"./transform.js\";\nexport { FileSystemSource } from \"./source.js\";\nexport { LruCache, DiskCache, makeCacheKey } from \"./cache.js\";\nexport {\n ImagoError,\n DimensionLimitError,\n UnsupportedFormatError,\n SourceNotFoundError,\n} from \"./errors.js\";\nexport type {\n ImagoOptions,\n TransformOptions,\n CacheOptions,\n SourceAdapter,\n Format,\n Fit,\n Gravity,\n} from \"./types.js\";\nexport { TransformOptionsSchema } from \"./types.js\";\n","import { statSync } from \"node:fs\";\nimport { TransformOptionsSchema, type ImagoOptions, type SourceAdapter, type TransformOptions } from \"./types.js\";\nimport { FileSystemSource } from \"./source.js\";\nimport { transform } from \"./transform.js\";\nimport { DiskCache, LruCache, makeCacheKey, type Cache } from \"./cache.js\";\nimport { ImagoError, SourceNotFoundError } from \"./errors.js\";\n\nfunction resolveSource(opts: ImagoOptions): SourceAdapter {\n if (typeof opts.source === \"string\") return new FileSystemSource(opts.source);\n return opts.source;\n}\n\nfunction resolveCache(opts: ImagoOptions): Cache {\n if (!opts.cache) return new LruCache();\n if (typeof opts.cache === \"string\") return new DiskCache(opts.cache);\n if (opts.cache.dir) return new DiskCache(opts.cache.dir);\n return new LruCache(opts.cache.maxItems ?? 256);\n}\n\nexport interface HandleResult {\n status: number;\n headers: Record<string, string>;\n body: Buffer;\n}\n\nexport async function handle(\n opts: ImagoOptions,\n key: string,\n rawQuery: Record<string, string>,\n reqHeaders: Record<string, string | undefined>,\n): Promise<HandleResult> {\n const source = resolveSource(opts);\n const cache = resolveCache(opts);\n const maxAge = opts.maxAge ?? 86400;\n\n const parsed = TransformOptionsSchema.safeParse(rawQuery);\n if (!parsed.success) {\n return jsonError(400, parsed.error.message);\n }\n const tOpts: TransformOptions = parsed.data;\n\n const fetched = await source.fetch(key);\n if (!fetched) {\n return jsonError(404, \"Source not found\", key);\n }\n\n let mtime = 0;\n if (typeof opts.source === \"string\") {\n try { mtime = statSync(`${opts.source}/${key}`).mtimeMs; } catch { /* ignore */ }\n }\n const cacheKey = makeCacheKey(key, tOpts, mtime);\n\n const cached = cache.get(cacheKey);\n const accept = reqHeaders[\"accept\"];\n let result;\n if (cached) {\n result = cached;\n } else {\n try {\n const transformed = await transform({\n source: fetched.buffer,\n options: tOpts,\n ...(accept !== undefined ? { accept } : {}),\n ...(opts.maxDimension !== undefined ? { maxDimension: opts.maxDimension } : {}),\n });\n result = { ...transformed, lastModified: new Date() };\n cache.set(cacheKey, result);\n } catch (err) {\n if (err instanceof ImagoError) return jsonError(err.status, err.message);\n throw err;\n }\n }\n\n const ifNoneMatch = reqHeaders[\"if-none-match\"];\n if (ifNoneMatch && ifNoneMatch === result.etag) {\n return { status: 304, headers: { ETag: result.etag }, body: Buffer.alloc(0) };\n }\n\n return {\n status: 200,\n headers: {\n \"content-type\": result.mimeType,\n \"cache-control\": `public, max-age=${maxAge}, immutable`,\n etag: result.etag,\n \"last-modified\": result.lastModified.toUTCString(),\n \"content-length\": String(result.buffer.length),\n },\n body: result.buffer,\n };\n}\n\nfunction jsonError(status: number, message: string, extra?: string): HandleResult {\n const body = Buffer.from(JSON.stringify({ error: message, key: extra }));\n return {\n status,\n headers: { \"content-type\": \"application/json\", \"content-length\": String(body.length) },\n body,\n };\n}\n\n// -------------------- Express --------------------\nexport function imago(opts: ImagoOptions) {\n return async (req: any, res: any, next: (err?: unknown) => void) => {\n try {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const key = decodeURIComponent(url.pathname.replace(/^\\//, \"\"));\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => { query[k] = v; });\n const result = await handle(opts, key, query, req.headers ?? {});\n res.status(result.status);\n for (const [k, v] of Object.entries(result.headers)) res.setHeader(k, v);\n res.end(result.body);\n } catch (err) {\n next(err);\n }\n };\n}\n\n// -------------------- Hono --------------------\nexport function imagoHono(opts: ImagoOptions) {\n return async (c: any) => {\n const url = new URL(c.req.url);\n const key = decodeURIComponent(url.pathname.replace(/^\\//, \"\"));\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => { query[k] = v; });\n const reqHeaders: Record<string, string> = {};\n c.req.headers.forEach((v: string, k: string) => { reqHeaders[k.toLowerCase()] = v; });\n const result = await handle(opts, key, query, reqHeaders);\n return new Response(result.body, { status: result.status, headers: result.headers });\n };\n}\n\n// -------------------- Fastify --------------------\nexport function imagoFastify(opts: ImagoOptions) {\n return async (req: any, reply: any) => {\n const url = new URL(req.url, \"http://localhost\");\n const key = decodeURIComponent(url.pathname.replace(/^\\//, \"\"));\n const query: Record<string, string> = {};\n url.searchParams.forEach((v, k) => { query[k] = v; });\n const result = await handle(opts, key, query, req.headers ?? {});\n reply.code(result.status);\n for (const [k, v] of Object.entries(result.headers)) reply.header(k, v);\n reply.send(result.body);\n };\n}\n","import { z } from \"zod\";\n\nexport const FORMATS = [\"jpeg\", \"png\", \"webp\", \"avif\", \"auto\"] as const;\nexport type Format = (typeof FORMATS)[number];\n\nexport const FITS = [\"cover\", \"contain\", \"fill\", \"inside\", \"outside\"] as const;\nexport type Fit = (typeof FITS)[number];\n\nexport const GRAVITIES = [\"center\", \"north\", \"south\", \"east\", \"west\", \"smart\"] as const;\nexport type Gravity = (typeof GRAVITIES)[number];\n\nexport const TransformOptionsSchema = z.object({\n w: z.coerce.number().int().min(1).max(4000).optional(),\n h: z.coerce.number().int().min(1).max(4000).optional(),\n fit: z.enum(FITS).default(\"cover\"),\n format: z.enum(FORMATS).default(\"auto\"),\n q: z.coerce.number().int().min(1).max(100).optional(),\n blur: z.coerce.number().min(0.3).max(1000).optional(),\n gravity: z.enum(GRAVITIES).default(\"center\"),\n strip: z.coerce.boolean().default(false),\n sharpen: z.coerce.boolean().default(false),\n placeholder: z.enum([\"blur\"]).optional(),\n});\nexport type TransformOptions = z.infer<typeof TransformOptionsSchema>;\n\nexport interface SourceAdapter {\n fetch(key: string): Promise<{ buffer: Buffer; mimeType: string } | undefined>;\n}\n\nexport interface CacheOptions {\n dir?: string;\n maxItems?: number;\n}\n\nexport interface ImagoOptions {\n source: string | SourceAdapter;\n cache?: string | CacheOptions;\n maxAge?: number;\n /** Maximum returned width or height. */\n maxDimension?: number;\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve, sep } from \"node:path\";\nimport { SourceNotFoundError } from \"./errors.js\";\nimport type { SourceAdapter } from \"./types.js\";\n\nexport class FileSystemSource implements SourceAdapter {\n readonly root: string;\n\n constructor(root: string) {\n this.root = resolve(root);\n }\n\n async fetch(key: string) {\n const abs = resolve(this.root, key);\n if (!abs.startsWith(this.root + sep) && abs !== this.root) {\n throw new SourceNotFoundError(key);\n }\n if (!existsSync(abs)) return undefined;\n const buffer = readFileSync(abs);\n return { buffer, mimeType: mimeFromExtension(abs) };\n }\n}\n\nfunction mimeFromExtension(path: string): string {\n const ext = path.toLowerCase().slice(path.lastIndexOf(\".\") + 1);\n switch (ext) {\n case \"jpg\":\n case \"jpeg\": return \"image/jpeg\";\n case \"png\": return \"image/png\";\n case \"webp\": return \"image/webp\";\n case \"avif\": return \"image/avif\";\n case \"gif\": return \"image/gif\";\n case \"bmp\": return \"image/bmp\";\n default: return \"application/octet-stream\";\n }\n}\n","export class ImagoError extends Error {\n readonly status: number;\n constructor(message: string, status: number) {\n super(message);\n this.name = \"ImagoError\";\n this.status = status;\n Object.setPrototypeOf(this, ImagoError.prototype);\n }\n}\n\nexport class DimensionLimitError extends ImagoError {\n constructor(public readonly requested: number, public readonly max: number) {\n super(`Requested dimension ${requested} exceeds maximum ${max}`, 413);\n this.name = \"DimensionLimitError\";\n Object.setPrototypeOf(this, DimensionLimitError.prototype);\n }\n}\n\nexport class UnsupportedFormatError extends ImagoError {\n constructor(public readonly format: string) {\n super(`Unsupported image format: ${format}`, 400);\n this.name = \"UnsupportedFormatError\";\n Object.setPrototypeOf(this, UnsupportedFormatError.prototype);\n }\n}\n\nexport class SourceNotFoundError extends ImagoError {\n constructor(public readonly key: string) {\n super(`Source image not found: ${key}`, 404);\n this.name = \"SourceNotFoundError\";\n Object.setPrototypeOf(this, SourceNotFoundError.prototype);\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { DimensionLimitError, UnsupportedFormatError } from \"./errors.js\";\nimport {\n type Format,\n type TransformOptions,\n TransformOptionsSchema,\n} from \"./types.js\";\n\ntype SharpInstance = {\n metadata(): Promise<{ width?: number; height?: number; format?: string }>;\n resize(opts: Record<string, unknown>): SharpInstance;\n rotate(): SharpInstance;\n blur(sigma: number): SharpInstance;\n sharpen(): SharpInstance;\n withMetadata(): SharpInstance;\n toFormat(fmt: string, opts: Record<string, unknown>): SharpInstance;\n toBuffer(): Promise<Buffer>;\n};\n\nlet _sharp: ((buffer: Buffer | Uint8Array) => SharpInstance) | undefined;\n\nasync function loadSharp() {\n if (!_sharp) {\n try {\n const mod = (await import(/* @vite-ignore */ \"sharp\")) as unknown as {\n default: (buffer: Buffer | Uint8Array) => SharpInstance;\n };\n _sharp = mod.default;\n } catch (err) {\n throw new Error(\n \"imago requires `sharp >= 0.33.0` as a peer dependency. Install it with `npm install sharp`.\",\n );\n }\n }\n return _sharp!;\n}\n\nconst QUALITY_DEFAULTS: Record<string, number> = {\n jpeg: 82,\n webp: 80,\n avif: 65,\n png: 90,\n};\n\nexport function pickFormat(requested: Format, accept: string | undefined, sourceFormat: string | undefined): Exclude<Format, \"auto\"> {\n if (requested !== \"auto\") return requested;\n const a = (accept ?? \"\").toLowerCase();\n if (a.includes(\"image/avif\")) return \"avif\";\n if (a.includes(\"image/webp\")) return \"webp\";\n if (sourceFormat === \"png\") return \"png\";\n return \"jpeg\";\n}\n\nexport interface TransformResult {\n buffer: Buffer;\n mimeType: string;\n etag: string;\n}\n\nexport interface TransformInput {\n source: Buffer;\n options: Partial<TransformOptions>;\n accept?: string;\n maxDimension?: number;\n}\n\nexport async function transform(input: TransformInput): Promise<TransformResult> {\n const opts = TransformOptionsSchema.parse(input.options);\n const maxDim = input.maxDimension ?? 4000;\n if (opts.w && opts.w > maxDim) throw new DimensionLimitError(opts.w, maxDim);\n if (opts.h && opts.h > maxDim) throw new DimensionLimitError(opts.h, maxDim);\n\n const sharp = await loadSharp();\n let pipeline = sharp(input.source).rotate();\n\n const meta = await pipeline.metadata();\n const finalFormat = pickFormat(opts.format, input.accept, meta.format);\n\n if (![\"jpeg\", \"png\", \"webp\", \"avif\"].includes(finalFormat)) {\n throw new UnsupportedFormatError(finalFormat);\n }\n\n if (opts.placeholder === \"blur\") {\n const buf = await sharp(input.source)\n .resize({ width: 8, height: 8, fit: \"cover\" })\n .blur(2)\n .toFormat(\"webp\", { quality: 50 })\n .toBuffer();\n return {\n buffer: buf,\n mimeType: \"image/webp\",\n etag: hashEtag(buf),\n };\n }\n\n if (opts.w || opts.h) {\n pipeline = pipeline.resize({\n ...(opts.w !== undefined ? { width: opts.w } : {}),\n ...(opts.h !== undefined ? { height: opts.h } : {}),\n fit: opts.fit,\n ...(opts.gravity === \"smart\" ? { position: \"attention\" } : { position: opts.gravity }),\n });\n }\n\n if (opts.blur !== undefined) pipeline = pipeline.blur(opts.blur);\n if (opts.sharpen) pipeline = pipeline.sharpen();\n if (!opts.strip) pipeline = pipeline.withMetadata();\n\n const quality = opts.q ?? QUALITY_DEFAULTS[finalFormat] ?? 80;\n pipeline = pipeline.toFormat(finalFormat, { quality });\n\n const buffer = await pipeline.toBuffer();\n\n return {\n buffer,\n mimeType: `image/${finalFormat}`,\n etag: hashEtag(buffer),\n };\n}\n\nfunction hashEtag(buf: Buffer): string {\n return `\"${createHash(\"md5\").update(buf).digest(\"hex\")}\"`;\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { TransformOptions } from \"./types.js\";\n\nexport interface CacheEntry {\n buffer: Buffer;\n mimeType: string;\n etag: string;\n lastModified: Date;\n}\n\nexport interface Cache {\n get(key: string): CacheEntry | undefined;\n set(key: string, entry: CacheEntry): void;\n}\n\nexport function makeCacheKey(sourceKey: string, opts: TransformOptions, mtime: number): string {\n const json = JSON.stringify({ sourceKey, opts, mtime });\n return createHash(\"sha1\").update(json).digest(\"hex\");\n}\n\nexport class LruCache implements Cache {\n private map = new Map<string, CacheEntry>();\n constructor(private readonly maxItems = 256) {}\n get(key: string) {\n const v = this.map.get(key);\n if (!v) return undefined;\n this.map.delete(key);\n this.map.set(key, v);\n return v;\n }\n set(key: string, entry: CacheEntry) {\n if (this.map.has(key)) this.map.delete(key);\n this.map.set(key, entry);\n while (this.map.size > this.maxItems) {\n const firstKey = this.map.keys().next().value as string | undefined;\n if (firstKey === undefined) break;\n this.map.delete(firstKey);\n }\n }\n}\n\nexport class DiskCache implements Cache {\n constructor(public readonly dir: string) {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n }\n private file(key: string) {\n return join(this.dir, key);\n }\n private meta(key: string) {\n return join(this.dir, `${key}.json`);\n }\n get(key: string): CacheEntry | undefined {\n if (!existsSync(this.file(key))) return undefined;\n try {\n const meta = JSON.parse(readFileSync(this.meta(key), \"utf8\")) as { mimeType: string; etag: string };\n const stat = statSync(this.file(key));\n return {\n buffer: readFileSync(this.file(key)),\n mimeType: meta.mimeType,\n etag: meta.etag,\n lastModified: stat.mtime,\n };\n } catch {\n return undefined;\n }\n }\n set(key: string, entry: CacheEntry) {\n writeFileSync(this.file(key), entry.buffer);\n writeFileSync(this.meta(key), JSON.stringify({ mimeType: entry.mimeType, etag: entry.etag }), \"utf8\");\n }\n}\n"],"mappings":"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","names":["import_node_fs","import_node_crypto","import_node_fs","import_node_path"]}
@@ -0,0 +1,141 @@
1
+ import * as undici_types from 'undici-types';
2
+ import { z } from 'zod';
3
+
4
+ declare const FORMATS: readonly ["jpeg", "png", "webp", "avif", "auto"];
5
+ type Format = (typeof FORMATS)[number];
6
+ declare const FITS: readonly ["cover", "contain", "fill", "inside", "outside"];
7
+ type Fit = (typeof FITS)[number];
8
+ declare const GRAVITIES: readonly ["center", "north", "south", "east", "west", "smart"];
9
+ type Gravity = (typeof GRAVITIES)[number];
10
+ declare const TransformOptionsSchema: z.ZodObject<{
11
+ w: z.ZodOptional<z.ZodNumber>;
12
+ h: z.ZodOptional<z.ZodNumber>;
13
+ fit: z.ZodDefault<z.ZodEnum<["cover", "contain", "fill", "inside", "outside"]>>;
14
+ format: z.ZodDefault<z.ZodEnum<["jpeg", "png", "webp", "avif", "auto"]>>;
15
+ q: z.ZodOptional<z.ZodNumber>;
16
+ blur: z.ZodOptional<z.ZodNumber>;
17
+ gravity: z.ZodDefault<z.ZodEnum<["center", "north", "south", "east", "west", "smart"]>>;
18
+ strip: z.ZodDefault<z.ZodBoolean>;
19
+ sharpen: z.ZodDefault<z.ZodBoolean>;
20
+ placeholder: z.ZodOptional<z.ZodEnum<["blur"]>>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ fit: "cover" | "contain" | "fill" | "inside" | "outside";
23
+ format: "jpeg" | "png" | "webp" | "avif" | "auto";
24
+ gravity: "center" | "north" | "south" | "east" | "west" | "smart";
25
+ strip: boolean;
26
+ sharpen: boolean;
27
+ w?: number | undefined;
28
+ h?: number | undefined;
29
+ q?: number | undefined;
30
+ blur?: number | undefined;
31
+ placeholder?: "blur" | undefined;
32
+ }, {
33
+ w?: number | undefined;
34
+ h?: number | undefined;
35
+ fit?: "cover" | "contain" | "fill" | "inside" | "outside" | undefined;
36
+ format?: "jpeg" | "png" | "webp" | "avif" | "auto" | undefined;
37
+ q?: number | undefined;
38
+ blur?: number | undefined;
39
+ gravity?: "center" | "north" | "south" | "east" | "west" | "smart" | undefined;
40
+ strip?: boolean | undefined;
41
+ sharpen?: boolean | undefined;
42
+ placeholder?: "blur" | undefined;
43
+ }>;
44
+ type TransformOptions = z.infer<typeof TransformOptionsSchema>;
45
+ interface SourceAdapter {
46
+ fetch(key: string): Promise<{
47
+ buffer: Buffer;
48
+ mimeType: string;
49
+ } | undefined>;
50
+ }
51
+ interface CacheOptions {
52
+ dir?: string;
53
+ maxItems?: number;
54
+ }
55
+ interface ImagoOptions {
56
+ source: string | SourceAdapter;
57
+ cache?: string | CacheOptions;
58
+ maxAge?: number;
59
+ /** Maximum returned width or height. */
60
+ maxDimension?: number;
61
+ }
62
+
63
+ interface HandleResult {
64
+ status: number;
65
+ headers: Record<string, string>;
66
+ body: Buffer;
67
+ }
68
+ declare function handle(opts: ImagoOptions, key: string, rawQuery: Record<string, string>, reqHeaders: Record<string, string | undefined>): Promise<HandleResult>;
69
+ declare function imago(opts: ImagoOptions): (req: any, res: any, next: (err?: unknown) => void) => Promise<void>;
70
+ declare function imagoHono(opts: ImagoOptions): (c: any) => Promise<undici_types.Response>;
71
+ declare function imagoFastify(opts: ImagoOptions): (req: any, reply: any) => Promise<void>;
72
+
73
+ declare function pickFormat(requested: Format, accept: string | undefined, sourceFormat: string | undefined): Exclude<Format, "auto">;
74
+ interface TransformResult {
75
+ buffer: Buffer;
76
+ mimeType: string;
77
+ etag: string;
78
+ }
79
+ interface TransformInput {
80
+ source: Buffer;
81
+ options: Partial<TransformOptions>;
82
+ accept?: string;
83
+ maxDimension?: number;
84
+ }
85
+ declare function transform(input: TransformInput): Promise<TransformResult>;
86
+
87
+ declare class FileSystemSource implements SourceAdapter {
88
+ readonly root: string;
89
+ constructor(root: string);
90
+ fetch(key: string): Promise<{
91
+ buffer: NonSharedBuffer;
92
+ mimeType: string;
93
+ } | undefined>;
94
+ }
95
+
96
+ interface CacheEntry {
97
+ buffer: Buffer;
98
+ mimeType: string;
99
+ etag: string;
100
+ lastModified: Date;
101
+ }
102
+ interface Cache {
103
+ get(key: string): CacheEntry | undefined;
104
+ set(key: string, entry: CacheEntry): void;
105
+ }
106
+ declare function makeCacheKey(sourceKey: string, opts: TransformOptions, mtime: number): string;
107
+ declare class LruCache implements Cache {
108
+ private readonly maxItems;
109
+ private map;
110
+ constructor(maxItems?: number);
111
+ get(key: string): CacheEntry | undefined;
112
+ set(key: string, entry: CacheEntry): void;
113
+ }
114
+ declare class DiskCache implements Cache {
115
+ readonly dir: string;
116
+ constructor(dir: string);
117
+ private file;
118
+ private meta;
119
+ get(key: string): CacheEntry | undefined;
120
+ set(key: string, entry: CacheEntry): void;
121
+ }
122
+
123
+ declare class ImagoError extends Error {
124
+ readonly status: number;
125
+ constructor(message: string, status: number);
126
+ }
127
+ declare class DimensionLimitError extends ImagoError {
128
+ readonly requested: number;
129
+ readonly max: number;
130
+ constructor(requested: number, max: number);
131
+ }
132
+ declare class UnsupportedFormatError extends ImagoError {
133
+ readonly format: string;
134
+ constructor(format: string);
135
+ }
136
+ declare class SourceNotFoundError extends ImagoError {
137
+ readonly key: string;
138
+ constructor(key: string);
139
+ }
140
+
141
+ export { type CacheOptions, DimensionLimitError, DiskCache, FileSystemSource, type Fit, type Format, type Gravity, ImagoError, type ImagoOptions, LruCache, type SourceAdapter, SourceNotFoundError, type TransformOptions, TransformOptionsSchema, UnsupportedFormatError, handle, imago, imagoFastify, imagoHono, makeCacheKey, pickFormat, transform };